pax_global_header00006660000000000000000000000064117211455360014517gustar00rootroot0000000000000052 comment=2d7e459eee64b672520d7576ea7d6c88313b6f83 ninix-aya-4.3.9/000077500000000000000000000000001172114553600134315ustar00rootroot00000000000000ninix-aya-4.3.9/COPYING000066400000000000000000000431101172114553600144630ustar00rootroot00000000000000 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-4.3.9/ChangeLog000066400000000000000000006441601172114553600152160ustar00rootroot00000000000000Wed 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/ を参照.) Wed January 11 2012 Shyouzou Sugitani * Application.find_ghost_by_name()の引数を2箇所直し忘れていたのを 修正した. 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-4.3.9/KNOWN_ISSUES000066400000000000000000000013611172114553600153440ustar00rootroot00000000000000共通: - 現在ありません。 POSIX(X11)環境: - ウインドウマネージャにMutter 3.0.2.1を使用した場合、ウインドウの最小化で \0側のウインドウしか最小化されない問題があります。 Windows環境: - pygtk 2.24.0にはマウスのダブルクリックが機能しない問題があります。 (http://www.mail-archive.com/pygtk@daa.com.au/msg20471.html) 2.24.1で修正されていますので、2.24.1以降のバージョンを使用して下さい。 - IDLEから起動した場合のみですが, 終了時にConsole, Preferences, NGM等の ウインドウが開いているとそれらのウインドウが残ったままハングアップ状態に なり正常に終了しません。 ninix-aya-4.3.9/MANIFEST.in000066400000000000000000000002171172114553600151670ustar00rootroot00000000000000include COPYING KNOWN_ISSUES ChangeLog NEWS README README.ninix TODO.ninix recursive-include doc *.txt recursive-include locale *.po prune bin ninix-aya-4.3.9/Makefile000066400000000000000000000020671172114553600150760ustar00rootroot00000000000000# # Makefile for ninix-aya # prefix = /opt/ninix-aya exec_libdir = $(prefix)/lib/ninix bindir = $(DESTDIR)$(prefix)/bin docdir = $(DESTDIR)$(prefix)/doc libdir = $(DESTDIR)$(exec_libdir) localedir = $(DESTDIR)$(prefix)/share/locale python = python NINIX = ninix all: install: install-lib install-bin install-doc install-lib: mkdir -p $(libdir) cp -r lib/* $(libdir) (cd $(libdir) ; $(python) -c 'import compileall; compileall.compile_dir(".")') mkdir -p $(localedir)/ja/LC_MESSAGES mkdir -p $(localedir)/zh_TW/LC_MESSAGES (cd locale ; msgfmt ja.po -o $(localedir)/ja/LC_MESSAGES/ninix.mo) (cd locale ; msgfmt zh_TW.po -o $(localedir)/zh_TW/LC_MESSAGES/ninix.mo) sed_dirs = sed -e "s,@python,$(python),g" -e "s,@libdir,$(exec_libdir),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 TODO.ninix KNOWN_ISSUES doc/extension.txt doc/kawari.txt doc/saori.txt COPYING ChangeLog $(docdir) clean: $(RM) bin/ninix *~ ninix-aya-4.3.9/NEWS000066400000000000000000001212631172114553600141350ustar00rootroot00000000000000Ver.4.3の変更点 ------------------- - Windows環境用にインストーラパッケージを配布するようにしました. もし, ご自分でインストーラをビルドする場合には次のコマンドを実行して下さい. python.exe setup.py bdist_wininst - Windows環境でも多言語対応が機能するようにしました. 例えば, 日本語環境ではメニュー等が日本語化されます. メッセージファイルはインストーラパッケージを使用してninix-ayaを インストールすれば一緒にインストールされます. (現在ja, zh_TWのみメッセージファイルが用意されています.) - ファイル名の変更がありますのでパッケージを作成する場合には注意して下さい. lib/main.py -> lib/ninix_main.py lib/sstplib.py -> lib/ninix/sstplib.py - ゴースト等のインストール機能がninix-aya本体に入りました. コンソールのInstallボタンを押してファイルを選択するか, ファイルをコンソールにDnDするとインストールが始まります. (コンソールはメニューの「設定」から開くことが出来ます.) これに伴い, ninix-installコマンドを削除しました. - プラグインをPOSIX/Windows両環境で動作する形で実装し直しました. (詳細についてはWebページ上で別途ドキュメントを公開予定です.) - Windows環境での動作を改善しました. - Windows環境ではゴーストの持つSHIORI DLLを使用するようにしました. ただし, 64bitのPythonではDLLを利用出来ません. 注意: 64bit Windows上の32bit Pythonは実行環境としては32bitなので DLLを利用出来ます. (将来はDLLと互換モジュールをユーザーが切り替えられるようにする予定です.) - \pタグを[]無しでも使えるように修正しました. - httplib2の使用をやめ, 以前のコードと同様の処理に戻しました. ですので, httplib2のインストールは不要になりました. - 本体とSHIORIのやりとりでの文字コードの処理方法を変更しました. (各リクエスト毎のCharsetエントリを使用しています. ninix-aya独自のSHIORIロード時の文字コードの問い合わせは削除しました.) - YAYAゴーストを複数起動した状態から1体でも終了すると, 残ったゴーストのYAYAが動作しなくなる問題を修正しました. - surfaces.txtのサーフェススコープ名の列記と省略形に対応しました. - surfaces.txtの同じIDのsurfaceエントリの分割に対応しました. - シェルを認識出来ないゴーストについてはとりあえず無視するようにしました. - Python3への移行の準備を始めました. 現在の動作環境はまだPython2.6もしくは2.7です. - 前回最後に起動していたゴーストの記録にはゴーストの名前ではなく インストール先ディレクトリ名を使用するようにしました. (互換性のため, ディレクトリ名での記録が無く名前での記録がある場合には 名前で探すようになっています.) また, シェルの選択が記録に反映されなくなっていたのを修正しました. - アイコン化解除イベントが間違って発行される場合があったのを修正しました. - \n[half]を使用するとバルーンの表示がおかしくなることがあったのを修正しました. - easyballoon互換モジュールで表示されるバルーンもサーフェスの倍率変更に 即追従するようにしました. - バルーンとサーフェスを隠す場合にはgtk.DrawingAreaを隠すのではなく, gtk.Windowを隠すように戻しました. (良く見ると分かるのですが, gtk.DrawingArea.hide()しても1ピクセル分の ウインドウが画面に残っていました.) - サーフェス倍率変更等の際のウインドウの位置計算を改善しました. (バルーン, 猫どりふ, きのこ, easyballoonも含む.) - バルーンの位置計算を簡素化した. - バルーンと猫どりふも画面から見切れることが出来るようにしました. - サーフェスと猫どりふに位置を初期化する機能(Ctrl-Shift-F12)を実装しました. それぞれキーボードフォーカスがある状態で上記のキーを入力すると初期位置に 戻ります. - NGMのゴーストインストール機能を再実装しました. - BalloonDescriptのwindowposition.x, windowposition.yに対応しました. (「ねこことショータRX」同梱のバルーン「ねこのきもち」の\1側の表示位置が 修正されました.) - エラー等のログの出力全てにloggingモジュールを使うように変更しました. - --debugオプションが値を取らないように変更しました. オプションを指定するとloggingモジュールを使用したログ出力のレベルが logging.DEBUGになります. - --logfileオプションで指定したファイルにログを出力することが可能になりました. - ゴーストwithバルーンのinstall.txt内でのballoon.source.directory指定に 対応しました. - KNOWN_ISSUESファイルを追加しました. この中には問題を抱えているソフトウエアの情報を記述してあります. Ver.4.2の変更点 ------------------- - 必要なソフトウエアが全て揃っていれば, Windows環境でも動作することを 確認しました. - GStreamerがインストールされていなくてもninix-ayaが動くようにしました. (Gstreamerが無ければ音声ファイルの再生は機能しません.) - 長い間ほとんど使用されることのなかったプラグインシステムを削除しました. (将来的には違う形でのプラグインの実装を考えています.) - デフォルトのSSTPポートから11000を削除しました. (デフォルトでは9801のみとなります.) - ninixとninix-installのコマンドラインオプション-H(--homedir)を削除しました. ninix-ayaのホームディレクトリはこれまでのデフォルトであった~/.ninixに 固定となります.(要望があればオプションで変更可能にします.) - ninix-installのコマンドラインオプション-A(--arcdir)を削除しました. - AYA Ver.5互換モジュールを改良しました. 「橘花」(taromati2)で音楽ファイルの再生が機能するようになりました. - 音楽ファイルのファイル名にマルチバイト文字が入っている場合に ファイルが認識されない問題を修正しました. - httplib2が必須ライブラリになりました. - httpc互換モジュールの致命的なバグ(無限ループ)を修正しました. - バルーンの半透明部分でマウス/キーボードの入力イベントが発生しなく なっていたのを修正しました. - Henryさんが作製したYAYAローダーを導入しました. YAYA(libaya5.so)を別途インストールする必要があります. (ninix-ayaの持つSAORI互換モジュールの呼び出しには未対応です.) - ninix-installでreadme.txtの入っていないゴーストアーカイブを インストールしようとすると落ちる問題を修正しました. - saori_cpuid互換モジュールを改良しました. - ninix-installが外部コマンドに依存しなくなりました. - ninix-installでファイルを展開する一時ディレクトリをユーザーが指定する オプション(--tempdir)を削除しました.(環境変数TMPDIRで設定可能です.) - 環境変数NINIX_USERと関連するオプションを削除しました. (デフォルトから変更していなければ影響ありません.) - 「何とかしてください」ウインドウ(仮)を追加しました. (ゴーストもしくはバルーンが存在しない場合にのみこの名前で現われます.) ウインドウに対してアーカイブをDnDすることでインストールが出来ます. - 里々互換モジュールを大幅に改良しました. - 「ゴーストwithバルーン」のバルーンのインストール先を仕様通り balloon/以下に変更しました. これに伴いninix-installに-r(--rebuild)オプションを追加しました. 4.1.3以前のninix-installでインストールしたバルーン付属ゴーストの バルーンをballoon/以下に移動します. 4.2を起動する前に実行しておくことを推奨します.(一回だけ実行すればOKです.) - シェルの変更はメニューの「交代」ではなく「シェル」から行なうように なりました. - デフォルトでPNAファイルを使用するように設定を変更しました. (最初にninix-ayaを起動した時に設定されるデフォルトです. 既にninix-ayaを起動している場合には, PNAファイルを「使用しない」 ようにデフォルトで設定されています. 値の変更はメニューの「設定」から出来ます.) - ninix-installでゴーストのreadme.txtとthumbnail.png(pnr)も インストールするようにしました. - ゴースト個別の設定ファイルSETTINGSを追加しました. 現在記録しているのは使用するバルーンの値のみです. バールン付属のゴーストはインストール時に付属バルーンが 使用するバルーンとしてこのファイルに記録されます. メニューからバルーンを変更すると値は上書きされます. - メニューからバルーンを変更した際にはすぐにバルーンの位置を 再計算するようにしました. - OnBalloonChangeイベントのReference1にバルーンの絶対パスを 入れるようにしました. - ninix-installの@ファイルリストのサポートを削除しました. (名前が@で始まるファイルにアーカイブを列記しておくと全て インストールされるというものです.) - ninix-installでinstall.txtをインストールしないように修正しました. - ネットワーク更新後のファイルの再読み込みで一部の変数に 更新前のデータが残っていたのを修正しました. - 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時代のもの?)や それ以外のゴースト/シェルでもオプション指定でスタンドアロンの シェルとしてインストール出来るようになっていました. 既にインストール済みのものについては動作を保証しません. - inverse時代のディレクトリ構成になっているアーカイブのサポートを ninix-installから全て削除しました. - ネットワーク更新対象ファイルのファイル名にマルチバイト文字が 含まれている場合に対応しました. (updates2.dauの中身はShift_JISと仮定して処理しています.) - SERIKOのoverlayfastに対応しました.(内部の処理はoverlayと全く同じです.) - SakuraScriptの\![set,wallpaper]に対応しました. (GNOMEデスクトップ環境のみの対応です. python-gconfを使用しています. この機能を利用しない場合はpython-gconfのインストールは不要です.) - サーフェスのマウスドラッグによる移動が終わった時に バルーンの位置を再計算するようにしました. - サーフェス倍率が100%以外の場合にバルーンオフセットの計算が 間違っていたのを修正しました. - 花柚互換モジュールでbackground.color設定に対応しました. - バルーンがちらつく問題を修正しました. (サーフェスについては4.1で修正済みです.) - 描画処理を全面的にCairoに移行しました. (バルーン, inputbox, communicatebox, teachbox, 使用率グラフ, サーフェスの当り判定領域(デバッグ用), NGMクローン, 「きのこ」, 「猫どりふ」, easyballoon, 花柚互換モジュール) - 「きのこ」と「猫どりふ」のウインドウが登場する際にフォーカスを 奪わないようにしました. - サーフェスのアニメーション処理を改良しました. Ver.4.1の変更点 ------------------- - Sakuraスクリプトの\4タグの動作を変更しました. これまでは相方から一定距離離れた地点まで移動してそれ以上は離れません でしたが, 元々離れている場合にはそこからさらに一定距離離れる方向に 移動するようにしました. - サーフェスとバルーンのウインドウが登場する際にフォーカスを奪わない ようにする処理を再度追加しました. (この設定は3.9.8bで削除したものと同じです. Gnome標準のウインドウ マネージャであるMetacity 2.27.2で試した限り意図通りに動いたので, 再度設定しました.) - サーフェスを移動する前にgtkのイベントが全て処理されるようにしました. (移動の前にウインドウサイズの変更があると, その処理が終わっているか どうかで移動の結果が変わってしまうためです.) - ウインドウをワークエリア外に出せない仕様のウインドウマネージャでも 画面の端を越えるウインドウの移動と同様の表現が出来るようにしました. - 花柚互換モジュールでタイトルの縦書きに対応しました. - キーボードからの入力を処理する辞書に特殊キーの識別子を追加しました. - これまではバルーンをダブルクリックした場合にもOnMouseDoubleClick イベントを発生させていましたが, 発生させないように変更しました. - Sakuraスクリプトの\xタグの処理を仕様書通りにしました. - OnBalloonChangeイベントのサポートを追加しました. (ただし, Reference1には空文字列が入っています.) - lzh形式アーカイブのサポートを削除しました. - NGMのデータベースのネットワーク更新中もゴーストが動作するよう改良しました. また, データベースの読み込みを高速化しました. - NGMが保存するDBファイルの文字コードをEUC-JPからUTF-8に変更しました. 自動的に変換されるのでユーザーは何もする必要はありません. - 新しいPluginの枠組みを作成しました. 使用方法はサンプルプラグイン「お天気やん」を参照して下さい. - バルーンの設定での"--n"形式の値への対応を削除しました. (ninix 0.1.6で導入されたものですが, 現在手に入る仕様には記述がありません.) - httpc.dll互換モジュールhttpc.pyを追加しました. - 使用していなかったPythonのdistutilsに関する部分をMakefileから削除しました. それに合わせてPKG-INFOファイルを削除しました. - マウスドラッグでサーフェスを移動した後のサーフェス位置の再計算の タイミングを変更して, 異常な位置にサーフェスが留まらないようにしました. - 画像を縮小する際の下限を8x8ピクセルに統一しました. - ninix独自のOnNinixReloading, OnNinixReloadedイベントを削除しました. - ninix独自のSSTPコマンドreloadを削除しました. - メニューからゴーストの再読み込みを削除しました. - ninix-updateコマンドを削除しました. ネットワーク更新はゴーストを起動して行なって下さい. Ver.4.0の変更点 ------------------- - ユーザー設定からヘルパーを削除しました. 音声ファイルの再生にはGStreamerを使用するようにしました. GStreamer Python bindingsが必要です. - れたす(lettuce.dll)互換モジュールを削除しました. 要望があれば再度GStreamerを使用して実装し直したものを追加します. - ユーザー設定からWebブラウザを削除しました. 使用している環境のデフォルトブラウザを呼び出します. - 「きのこ」の'ontop'の処理が確実に行なわれるように修正しました. - 「猫どりふ」の見切れをゴースト同様に透明部分も含めたサーフェスの1/3が 画面外に出ると発動するよう変更しました. - ユーザー設定の項目を減らしました. SHIORIイベントの発生を抑制する項目(PREFS_EVENT_KILL_LIST)と マウスボタンの機能を設定する項目(PREFS_MOUSE_BUTTON1, PREFS_MOUSE_BUTTON3)を 削除しました. また, メニューから個々のゴーストについてサーフェス倍率とスクリプトの 再生スピードを設定する項目を削除しました.(全ゴーストが同じ設定になります.) - 画面の上下方向の有効範囲(タスクバー等を除いた範囲)をユーザーが指定する ための設定値を削除しました. ウインドウマネージャから情報を得て自動的に 処理します. - sourceforge.jpにGitリポジトリを作成し, ソースコードの管理をGitに移行しました. 今後CVSのリポジトリは更新されません. - バルーンを出す際にバルーンがフォーカスを奪わないようにするための設定を 削除しました. バルーンを出す際にバルーンがフォーカスを奪わないようにしつつ, バルーンの ウインドウを前面に持ってくる方法が見付からなかったためです. この辺りの処理はウインドウマネージャ次第で変わってしまい, 確実な方法が 無いので, ninix-ayaでは何もしないことにしました. - サーフェスのツールチップ表示を実装しました.(SSP互換) - surfaces.txtの文字コード指定に対応しました.(SSP互換) - _niseshiori.so(暗号化辞書の解読用C言語モジュール)を削除しました. ninix-aya本体にはC言語モジュールが無くなりました. - 透過ウインドウ処理にGTK+2.10の新機能を使用するように変更しました. このバージョンから正式にサポートされた機能になります. - ゴーストのサーフェスをマウスドラッグするボタンを左ボタン(1番)に変更しました. - ゴーストのサーフェス移動後の位置の再計算のタイミングを移動直後にしました. - osuwari.dll互換SAORIモジュールosuwari.pyを追加しました. - OnBallonCloseイベントのサポートを追加しました. - OnMouseEnterAll, OnMouseLeaveAll, OnMouseEnter, OnMouseLeave イベントの サポートを追加しました. - SERIKOによるサーフェスの書き換えを抑制するオプションを追加しました. (サーフェスの書き換えが起きないだけで内部でSERIKOは動作しています.) - YAYAローダー(yaya.py)を追加しました.(動作確認がまだ出来ていません.) - メニューコンテキストの配置がSSPに近付くよう変更しました. - PNAファイルの処理にNumpyの機能を使い高速化しました. - アニメーションの処理を変更し, CPU負荷とコマ飛びを低減しました. Ver.3.9の変更点 ------------------- - 「美坂」互換モジュールに文字コードの自動判定を実装しました. Shift_JIS以外の文字コードを使用しているゴーストを動作させることが 可能になりました. 機能させるには Universal Encoding Detector (http://chardet.feedparser.org/) が必要になります. - 上付き, 下付き, 下線および字消し線のフォントタグを実装しました. - 括弧の無い(\p0のような)\pタグに対応しました. - ゴーストの見切れ処理を調整しました. 透明部分も含めたサーフェスの1/3が 画面外に出ると発動するようになりました. (これまでは1/4でした.) - 「文 Ver.5」互換モジュールを追加しました. AYA5 DLL 付属の aya_shiori3.dic を使っているゴーストはほぼ動きます. Real AYA5 ローダーは削除しました. - コミュニケート複数送信拡張に対応しました. - タスクバー上にサーフェスウインドウ2つが表示されるのをゴースト1組で1つ 表示されるようにしました. - バルーンがアクティブウインドウより下に表示されるよう調整しました. - バルーンの設定ファイルによるフォントサイズの指定とユーザーによる指定を きちんと区別し, ユーザー指定は3/4倍しないように修正しました. - サーフェスとバルーンの描画処理の一部に Cairo グラフィックライブラリ (http://cairographics.org/) を使用するよう変更しました. そのため GTK+(pygtk) 2.8 以降が必要となります. この変更の結果, デスクトップ側の環境が対応していれば, pna もしくは 本体設定によるサーフェスとバルーンの半透明化が可能です.(*) X Window System であれば Composite 拡張と composition manager (xcompmgr, etc.) が必要です. (*)注記: Shape 1.1 拡張に未対応のため表示が乱れます. - ddp暗号化ファイルのサポートを追加しました. (「理夢」がサーフェスで使用しています.) Ver.3.8の変更点 --------------- - メニューフォアグラウンドの画像およびフォントカラー設定に対応しました。 (正しく動作させるには GTK+ 2.6 以降が必要です。) - SHIORIがNOTIFYイベントに対してスクリプトを返しても破棄するよう修正しました。 - easyballoon もサーフェスの倍率に合わせて縮小(拡大)するようにしました。 「猫どりふ」などとは違い、サーフェスの倍率を変更しても、既に生成されている バルーンは生成時点の倍率のままです。 - ninix-aya 終了後、即座に SSTP サーバのソケットを削除するようにしました。 - サーフェスの描画処理を改良しました。 - アニメーションの処理を改良しました。 - サーフェス、バルーン等の縮小でサイズが 0 にならないように修正しました。 - mciaudio および mciaudior のファイル名の処理を修正しました。 - ファイルがドロップされた際の処理を OnFileDrop2 に変更しました。 本体で設定されているヘルパーへの引き渡しはイベントの結果にかかわらず 実行されません。 (上記の mciaudior の修正と合わせて「橘花」の音声ファイル再生が 動くようになりました。) Ver.3.6の変更点 --------------- - READMEファイルをREADMEとNEWSに分割しました。 - SERIKOの処理を改良しました。 - コーディングスタイルを調整しました。 - OnBoot等のイベントでスクリプト終了時までにサーフェス定義が無かった場合には \0, \1のみ強制的にサーフェスを表示するようにしました。 - 起動後サーフェスがまだ指定されていない状態で\iタグが来た場合に、 デフォルトIDに対する指定として受け付けてしまっていた問題を修正。 - %ms, %mc, %mz等をSHIORI/3.0として処理する上で、 IDに\ms, \mc, \mzの様に \を付けるよう修正しました。 - ゴーストマネージャが~/.ninix/ngm/data/MasterList.xmlが無いと 落ちる問題を修正しました。 - 「華和梨7」互換モジュールを改良しました。 - SHIORI/3.0 basewareversionをSHIORIのロード時に通知するようにしました。 (Reference2には開発コードを除いた数値のみのバージョン番号が入っています。) - OnShellChangedのReference1、Reference2を追加しました。 - OnShellChangingのReference1を追加しました。 - ポップアップメニューを出すかどうかを決めるリクエストの処理を追加しました。 - \![set,windowstate,stayontop]、\![set,windowstate,!stayontop]を実装しました。 - \![set,windowstate,minimize]を実装しました。 - OnMouseClickイベントの処理をSHIORI/3.0仕様に準拠しました。 - 本体設定に「喋る時手前に出てくる」設定を追加しました。 設定されている場合には喋り始める時に一回だけサーフェスとバルーンを 手前に出します。この変更に合わせて喋っている間常にサーフェスと バルーンを手前に出していた処理を削除しました。 - ゴースト起動時のOnDisplayChangeは喋らないように修正しました。 - \v(手前に出てくる)タグを実装しました。 - \sタグでサーフェスIDが数値で指定されていて、先頭に0が付いている場合は 0を削除する("0001"なら"1"に置き換える)ようにしました。 - \_uタグの処理を実装しました。 - サーフェスとバルーンのアルファチャンネル設定を追加しました。(未テスト) - ゴースト側で指定されたバルーン名がバルーンのインストール ディレクトリ名の場合にも対応しました。 - CheckQueueコマンドを拡張しました。 キューに残っている全てのリクエストの数も返します。 - 本体設定に「喋り終わると裏へ沈む」設定を追加しました。 - 使用率トップのゴーストのai.pngを使用率グラフの背景に使用するようにしました。 - 「美坂」互換モジュールを改良しました。(「橘花」対応のため。) Ver.3.4の変更点 --------------- - GTK+2.4 APIに移行しました。 - 「華和梨8」でのマルチゴーストに対応しました。 (_kawari8.soも対応したものが必要になります。従来のものは動作しません。) - スクリプトエラーからの復帰処理を追加しました。 - 単体のシェルを使用すると落ちる問題を修正しました。 - 「文」のバージョン判定を修正しました。 - \![change,ghost]による自分自身への交代が正しく処理されるよう修正しました。 - メニューのサイドバーとフォントカラー変更の実装を修正しました。 - サーフェスのリセットで自由配置が無効になるバグを修正しました。 - サーフェスのリセットの際に必要以上にオーバーレイ等を消去してしまうのを修正 しました。 - PNAファイルによるサーフェスとバルーンのアルファチャンネル設定に対応しました。 (オーバーレイ等についても対応しています。本体の透過処理にはXサーバが Composition拡張機能を持ち、適切に設定されていることが必要です。) - 本体設定でPNAファイルを使用するかどうかを設定できるようにしました。 - openngmをベースに「何かゴーストマネージャ」のクローンを実装しました。 (未完成のため検索とネットワーク更新機能のみです。) Ver.3.2の変更点 --------------- - 「きのこ」互換機能を追加しました。スキンファイルはninix-installでインストール することができます。 - 「猫どりふ」互換機能を追加しました。スキンおよび落下物ファイルはninix-installで インストールすることができます。 (未完成のため落下物を落としてSHIORIイベントを発生させることしかできません。) - サーフェスの当たり判定領域の表示/非表示を本体設定から変更できるようにしました。 - ホームディレクトリが同じninix-ayaの多重起動を禁止しました。 - アニメーションのバグを修正しました。 - 「里々」互換モジュールを改良/修正しました。 - 3.0で'\-'タグが機能しなくなっていたのを修正しました。 - SSTPのEXECUTE/1.0にCheckQueueコマンドを追加しました。 - 「文」のバージョン判定を修正しました。 Ver.3.0の変更点 --------------- - 「文5」モジュールインタフェース。 - ポップアップメニューからゴーストを複数起動できるようにしました。 - SSTP Bottleクライアント向けにGetNamesコマンドを追加しました。 - SSTP SEND/1.4の処理でIfGhost指定されたゴーストが起動していない場合には 一時的にそのゴーストを呼び出してスクリプトを再生するようにしました。 (指定ゴーストがインストールされていない場合に他のゴーストでスクリプトを 再生するかどうかは本体設定の「色々」->「SSTP 設定」で変更できます。) - アニメーションの処理を改良しました。(SERIKO/2.0にも対応。) - フォントの設定ファイルpango_fontrcをpreferencesに統合しました。 設定は従来通り本体設定から行なうようになっています。 - これまではポップアップメニューから他のゴーストの専用バルーンも含めた全バルーンが 選択できたのをそのゴーストの専用バルーンと汎用バルーンに制限しました。 (デフォルトバルーンは汎用バルーンからのみ選択可能です。) これは複数ゴースト起動が可能になったための変更で、消滅したゴーストの専用バルーン を使用しているゴーストがいるという状況が発生しないようにするための措置です。 - サーフェス倍率と表示ウエイトのデフォルト設定は本体設定で行なうようになりました。 ポップアップメニューからの変更は対象となるゴーストの設定を一時的に変更するだけに なります。他のゴーストや次回起動時の設定には影響しません。 - -Rオプションを削除しました。 - Pythonのトレースバック出力専用ダイアログを追加しました。 - オーナードローメニュー用画像をポップアップメニューで利用するようにしました。 Ver.2.6の変更点 --------------- - 「里々」互換モジュールを大幅に更新しました。 - 「里々」互換モジュールで SAORI 互換モジュール呼び出しに対応しました。 - saori_cpuid.dll 互換モジュールを追加しました。 - ssu.dll 互換モジュールを追加しました。 - 「花柚」互換モジュールで radar 形式のグラフに対応しました。 - メニューアイテムにアクセラレータを設定しました。 - デフォルトバルーンの設定に「常にこのバルーンを使う」チェックボックスを 追加しました。 - OnChoiceEnter イベントのサポートを追加しました。 - 「文」互換モジュールを Ver. 4.97fix0 仕様に更新しました。 - 明示的にサーフェス指定が来ない限りサーフェスを出さないように変更しました。 - cjkcodecs(要1.0.2)にも対応しました。 - 「華和梨」、「偽栞」互換モジュールのメタ文字列の展開を改良しました。 - サーフェスの当り判定の ID を0〜255に拡張しました。 - バルーン切り替えの際にゴーストを再起動しないように変更しました。 - 表示する文字が含まれていないスクリプト(改行のみなど)の場合にはバルーンを 出さないようにしました。 - バルーンのフォントサイズの設定が実際の表示に反映されるよう修正しました。 - バルーンフォント設定の変更が即反映されるように修正しました。 - %username の展開で最初に SHIORI で設定されている値を問い合わせるように しました。 - 「ポータル」、「おすすめ」の中の項目を選択した際に OnRecommandedSiteChoice イベントを発生させるようにしました。 - バルーンの配置を改良しました。 Ver.2.4の変更点 --------------- - サーフェス倍率の最小値を10%から40%に変更しました。 - サーフェスに合わせてバルーンも縮小できるようにしました。 - 本体設定にデフォルトバルーンの設定を追加しました。 それに合わせてポップアップメニューのバルーンの項目は起動中のゴーストの バルーンを一時的に変更するのみにしました。 - 起動時点でサーフェス全てを読み込んでいたのを必要になってから読み込むように 変更しました。起動時間の短縮、メモリ使用量の削減などの効果があります。 ただし一部アニメーション等で動作が遅くなることがあります。 - passivemode 中に SSTP を受信した場合には passivemode を抜けるまで再生を 始めないよう修正しました。 - サーフェス移動中にゴーストの動作を停止させないよう変更しました。 - wmove.dll 互換モジュールを追加しました。 - UNIX ドメインソケット版の SSTP サーバを追加した際に入ったバグを修正しました。 - ポップアップメニューに「ポータル」、「おすすめ」を実装しました。 - Shell の descript.txt の seriko.alignmenttodesktop に対応しました。 - 全てのデスクトップに居座るようにする設定をポップアップメニューに追加 しました。ウインドウマネージャによっては正しく機能しないことがあります。 - 起動後に本体設定で画面下端からの距離を調整できるようにしました。 (従来通り -R オプションも使用可能ですが、このオプションは将来削除される 可能性があります。オプションを指定した場合はそれが優先されます。) また画面上部に移動するゴースト向けに画面上端からの距離も指定できるように しました。 easyballoon 互換モジュールもこれらの設定の影響を受けます。 - Python2.3 で動作するようにしました。 - easyballoon 互換モジュールを1.0仕様に対応させました。 Ver.2.2の変更点 --------------- - ゴーストが最小化されている時はしゃべらなくなりました。(時間経過イベントは 発生しています。) SSTP リクエストに対しては "512 Invisible" を返します。 - Python栞のサポートを削除しました。 - 本体のターミナル出力を UTF-8 に変更しました。互換栞等はそれぞれが内部で 使用している文字コードでの出力のままです。 - install.txt で指定されるインストールディレクトリの文字コードを UTF-8 に 変換するよう変更しました。 - ファイル名に関して文字コード変換を行なわないよう変更しました。 アーカイブ内にマルチバイト文字を使用したファイル名があるものについては ゴースト・シェル等の種類を問わずサポート対象外です。 - メニュー等を L10N (gettext化)しました。メッセージカタログは日本語(ja.po)と 中国語(zh_TW.po)が用意されています。(zh_TW.poはChieh-Nan Wangさんが作成。) - バルーンに使用しているウインドウの種類がダイアログに変わりました。 - コミュニケートウインドウ, 花柚ウインドウの移動の処理が変更になりました。 よりスムーズに移動できるようになっています。 - サーフェス・バルーン・コミュニケートウインドウがサイズ変更を拒否するよう にしました。 - 本体・SSTP サーバ・互換 SAORI 内部で使用する文字コードを Unicode に変更 しました。 - 華和梨7で日本語以外の文字コードを使用したゴーストにも対応しました。 - サーフェスが最小化された場合と復帰した場合にイベントが発生します。 - ninix-install で pnaファイルもインストールするようにしました。 ネットワーク更新の対象としても認識されるようになっています。 - ninix 用プラグインの定義ファイル plugin.txt で EUC-JP 以外の文字コードを 使用可能にしました。 (デフォルトは EUC-JP なので既存のプラグインの変更は不要です。) - ゴースト起動時に OnDisplayChange を送信するようにしました。 - ~/.ninix/gtkrc をインストールしないようにしました. ファイルは doc/examples に移動しています。 - ゴーストのアイコンをサーフェスウインドウのアイコンとして使うようにしました。 ポップアップメニューでも使っています。 - misaka.py を「フサギコ漫談」対応のため変更しました。 - サーフェス画像として暗号化 PNG を使えるようにしました。 - 着せ替えメニューをポップアップメニューから分離して画面上に置いておける ようにしました。 Ver.2.0の変更点 --------------- - GTK+1.2 から GTK+2.0 API に移行しました。 Python 2.0, GTK+2.0, PyGTK 1.99 以降が必要です。 (Python 2.2, GTK+ 2.2.1, PyGTK 1.99.14 以降推奨。) - libpng を使用していた _image.so モジュールは削除されました。 ビルドに libpng のヘッダは不要です。 - サーフェス縮小機能を搭載しました。 ポップアップメニューの「サーフェス倍率」から選択して下さい。 - 画像ファイルの xpm 形式への変換および変換してインストールされたファイルの サポートが削除されました。 変換を使用していた場合はゴーストを再インストールする必要があります。 - ゴーストインストーラ(ninix-install)の動作が変更になりました。 ゴーストのインストール先となるディレクトリの決定方法が従来と異なり ゴーストアーカイブ内の install.txt に従うようになっています。 既にインストールされているゴーストはそのままで動作しますので 再インストールの必要はありません。 - ゴースト起動時間と消滅回数の記録先が ~/.ninix/history, ~/.ninix/vanished からゴーストのインストール先の HISTORY ファイルに変更になりました。 以前のファイルの記録は引き継がれません。 また、これまでのサーフェス毎の起動時間ではなくそのディレクトリにある サーフェス全てについての起動時間の合計のみが記録されるようになっています。 - バルーンフォントの指定がPango形式に変更になっています。指定は従来通り ポップアップメニューの「設定」から行なえます。 設定ファイルは ~/.ninix/pango_fontrc になります。 - CROW 同梱ゴーストのインストール機能は削除されました。 - マウスカーソルがサーフェスの当り判定領域に入るとカーソル形状が変わります。 撫で/触り判定はボタンを押していない状態でマウスを動かすと発生するように 変更されています。 - バルーンのスクロールボタンがマウスホイールで操作できるようになりました。 - -p オプションは廃止されました。ウインドウマネージャで強制しない限り ウインドウの枠などは付きません。また、ウインドウの配置も可能な範囲で ウインドウマネージャの制御を受けないようになっています。 - 着せ替え対応。 Ver.1.0の変更点 --------------- - 「文」互換 SHIORI モジュール aya.py。 - SAORI API 互換フレームワークおよび各種 SAORI 互換モジュール。 (各モジュールの詳細は doc/saori.txt を参照して下さい。) - 「華和梨8」モジュールインタフェース。 (「華和梨8」モジュールの詳細は doc/kawari.txt を参照して下さい。) - ゴースト間コミュニケーション対応。 - アニメーション機能強化。 - ネットワーク更新の途中キャンセル対応。 - SakuraScript 対応強化。 - 画面の上端や自由配置といった位置指定に対応。 aya.py は、umeici 氏が開発公開されている「文(あや)」Ver. 3.x〜4.xの 仕様に準拠した互換モジュールです。基本的な構造は「偽林檎」を参考に させていただきました。 ninix-aya-4.3.9/README000066400000000000000000000134661172114553600143230ustar00rootroot00000000000000-------------------------- ninix-aya(機能拡張版ninix) -------------------------- これは何か ---------- ninix-aya(以前は「文」互換モジュール等 for ninixと呼ばれていました)は、 UNIX系OS対応デスクトップアクセサリninixに拡張機能を追加したパッケージです。 また、オリジナルninixに収録されていない改良、バグフィックスも併せて 収録しています。 現在はUNIX系OSに限らずWindowsでも動作するようになっています. 必要なもの ---------- 本ソフトウェアを動作させるには以下のソフトウェアが必要です。 動作確認済みのバージョンより古いものでは動かない場合があります。 (KNOWN_ISSUESに問題を抱えているソフトウエアの情報があります。) - Python (http://www.python.org/) 本ソフトウェアの開発言語です。 バージョン 2.7.2 での動作を確認しています。 - GTK+ (http://www.gtk.org/) クロスプラットフォームの GUI ライブラリです。 バージョン 2.24.5 での動作を確認しています。 - Numpy(http://numpy.scipy.org/) Python 用の数学関数ライブラリです。 バージョン 1.4.1 での動作を確認しています。 - PyGTK (http://www.daa.com.au/~james/pygtk/) Python から GTK+ を利用するためのライブラリです。 バージョン 2.24.0 での動作を確認しています。 Numpy support が必須です。 Numpy をインストールした上で PyGTK がビルドされている必要があります。 - Python for Windows extensions(http://sourceforge.net/projects/pywin32/) Windows 環境でのみ必要なライブラリです。 バージョン 216 での動作を確認しています。 - GStreamer Python bindings (http://gstreamer.freedesktop.org/src/gst-python/) このソフトウェアはninix-ayaを動作させるのに必須ではありません。 音声ファイルの再生に使用しています。 バージョン 0.10.19 での動作を確認しています。 - Universal Encoding Detector (http://chardet.feedparser.org/) このソフトウェアはninix-ayaを動作させるのに必須ではありません。 Shift_JIS以外の文字コードを使用している「美坂」使用ゴーストを動作させる場合と httpc.dll互換モジュールを動作させる場合に必要です。 バージョン 2.0.1 での動作を確認しています。 - Python bindings for the GConf (http://www.pygtk.org/) このソフトウェアはninix-ayaを動作させるのに必須ではありません。 GNOMEデスクトップ環境でSakuraScript(\![set,wallpaper])による壁紙設定機能を 利用する場合にのみ必要になります。 バージョン 2.28.1 での動作を確認しています。(Python GNOMEの中にあります。) インストール ------------ Windows環境では必要なソフトウエアをインストールした後に、Windows用の インストーラパッケージをダウンロードして実行すればインストール完了です。 各種UNIX系OS用パッケージを使用する場合は ninix-aya 開発プロジェクト (http://ninix-aya.sourceforge.jp/)のパッケージ情報を御覧下さい。 以下ではソースアーカイブからインストールする方法について説明します。 (UNIX系OSを想定しています。) 配布サイトよりソースアーカイブの最新版を入手し、展開します。 以降の作業手順は ninix のインストールとほぼ同じです。 以下のように make コマンドを実行すればインストールは完了です。 # make # make install インストール先ディレクトリ (prefix) は /opt/ninix-aya になってい ます。必要に応じて変えて下さい。例: # make install prefix=/usr/local python コマンドの名前も同様に変更できます。ここで指定した名前は 実行時にも使われます。 # make install python=/opt/bin/python2.6 インストールされる ninix コマンドと ninix-install コマンドの名前 は二つの変数 NINIX, NINIX_INSTALL でそれぞれ変更できます。例: # make install NINIX=ninix-aya-4.0 NINIX_INSTALL=ninix-aya-install-4.0 また、$(prefix)/bin に相当するディレクトリをパスに追加して下さい。 ライセンス ---------- Copyright (C) 2001, 2002 by Tamito KAJIYAMA Copyright (C) 2002-2007 by MATSUMURA Namihiko Copyright (C) 2002-2012 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://sourceforge.jp/projects/ninix-aya/ 上のフォーラムへどうぞ。 (過去の開発記録の一部は http://nie.counterghost.net/ にあります。) リンク ------ あれ以外の何か with "任意" のページ(ninix配布サイト) http://www.geocities.co.jp/SiliconValley-Cupertino/7565/ さくらのにえ http://nie.counterghost.net/ ninix-aya開発プロジェクト http://ninix-aya.sourceforge.jp/ ninix-aya配布サイト http://sourceforge.jp/projects/ninix-aya/ 以上 ninix-aya-4.3.9/README.ninix000066400000000000000000001676251172114553600154560ustar00rootroot00000000000000ninix 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-4.3.9/TODO.ninix000066400000000000000000000014551172114553600152520ustar00rootroot00000000000000TODO ==== Tamito KAJIYAMA <24 June 2001> Ʊ (ͥǤϤʤ) o ɸΥȤѰդ (ǤХեդ) o եɥå˥ե0/10֤ᤵʤ o Х롼֥Хåե󥰤褹褦ˤ (®?) o Х롼ɽΰηˡθľ o SSTP ޡSSTP å륫ΰַν o NINIX_HOME ñǥ쥯ȥ꤫ʣǥ쥯ȥ˳ĥ o ninix-install ˥󥹥ȡϢ٥Ȥȯ륪ץɲ o SHIORI/2.3b (ƱΤΤ٤) o SHIORI/2.4 (ñؽǽ) o SERIKO/1.3 (夻ؤ) o SERIKO/1.5SERIKO/1.9SERIKO/1.10 (˥᡼) o updates.txt б (?) o JPEG Υե/Х롼б (?) o إåɥ饤󥻥󥵵 (?) o ޥ˥奢ڡѰդ - ninix.1 - ninix-install.1 - pngtoxpm.1 ninix-aya-4.3.9/bin/000077500000000000000000000000001172114553600142015ustar00rootroot00000000000000ninix-aya-4.3.9/bin/ninix.in000066400000000000000000000000671172114553600156610ustar00rootroot00000000000000#!/bin/sh exec @python @libdir/ninix_main.py ${1+"$@"} ninix-aya-4.3.9/doc/000077500000000000000000000000001172114553600141765ustar00rootroot00000000000000ninix-aya-4.3.9/doc/extension.txt000066400000000000000000000173301172114553600167570ustar00rootroot00000000000000ninix の実装と独自拡張について ============================== SSTP (Sakura Script Transfer Protocol) -------------------------------------- SEND/1.3 の実装 - - - - - - - - SEND/1.3 の HWnd: ヘッダは、オリジナルの定義ではウィンドウハンド ルの番号を表しますが、ninix の実装においては Unix ドメインソケッ トのアドレス (絶対パス名) を表します。SSTP クライアントはリクエ ストを送信する前に予め Unix ドメインソケットを作成して接続を受け 付ける状態にしておかなければなりません。例: SEND SSTP/1.3 Sender: client.py HWnd: /tmp/.client-unix Script: \h\s[0]調子どう?\n\n\q0[#a0][まあまあ]\q1[#a1][イマイチ]\m[wait] Entry: #a0,\m[OK]\h\s[0]ふーん。\m[end]\e Entry: #a1,\m[NG]\h\s[0]酒に逃げるなヨ!\m[end]\e Charset: Shift_JIS [EOD] SSTP サーバは、HWnd: ヘッダで指定された Unix ドメインソケットを 通じてスクリプト中に現れた \m[] タグの引数を SSTP クライアントに 送り返します。上記の例で最初の選択肢が選ばれた場合、SSTP サーバ は /tmp/.client-unix に接続して以下のデータを送信します。 +wait\n +OK\n +end\n -\n データは改行文字 \n (ASCII コード 0x0a) で区切られ、各行は一つの パケットを表します。パケットの最初の一文字はそのパケットの種類を 表します。 + \m[] タグの引数を送るパケット - 通信終了を通知するパケット \m[] タグの引数には Sakura Script のタグ以外の任意の文字列を渡す ことができます。%selfname などのメタ文字列は展開されて返されます。 EXECUTE/1.5(*注1) - - - - - - (*注1) ninix-aya 4.0.3以降削除されました SSTP に拡張リクエスト EXECUTE/1.5 が追加されています。このリクエ ストを受け取ると SSTP サーバはデータの再読み込みを開始します。例: EXECUTE SSTP/1.5 Sender: ninix-install Command: Reload Charset: Shift_JIS [EOD] 追加されたコマンドは以下の通りです。 Reload データの再読み込みを開始する このリクエストは SSTP サーバが動いているホストからのみ送ることが できます。その他のホストからのリクエストに対しては 420 Refuse が 返されます。 SHIORI 拡張イベント(*注2) ------------------- (*注2) ninix-aya 4.0.3以降削除されました 以下の拡張イベントを発行するようになっています。 OnNinixReloading データの再読み込みを開始した OnNinixReloaded データの再読み込みが終了した プラグイン(*注3) ---------- (*注3) ninix-aya 4.2以降削除されました ninix-aya 4.3以降の新しいプラグインについてはここでは述べません ninix 用のプラグインを ninix-install コマンドでインストールする ことにより任意の機能を本体に追加することができます。 ninix-install コマンドでインストール可能なファイルは、プラグイン 本体 (実行可能なプログラムファイル) と plugin.txt ファイルを含む アーカイブファイル (zip 形式または lha 形式(*注4)) です。 plugin.txt はプラグインの名称や起動方法を記述したテキストファイ ルです。文字コードは EUC-JP です。(*注5) 例: # sstpnews plugin 1.0 name: SSTP News startup: sstpnews.py,--fetch menuitem: ニュースを読む,sstpnews.py,--fetch menuitem: バージョン情報,sstpnews.py,--version # で始まる行はコメントです。コメント行と空行は無視されます。 name: ヘッダはプラグインの名前 (識別子) を表します。このヘッダは 必須です。 startup: ヘッダは本体の起動後に一度だけ実行されるコマンドを表し ます。実行ファイル名と引数をコンマ (,) で区切って指定して下さい。 このヘッダは複数指定することはできません。 menuitem: ヘッダは本体の右クリックメニューに追加する項目を表しま す。メニューの項目名、実行ファイル名、引数をコンマで区切って指定 して下さい。このヘッダは複数指定することができます。 startup: ヘッダと menuitem: ヘッダはいずれも省略可能です。 プラグインの実行時のカレントディレクトリは実行ファイルの置かれた ディレクトリになります。プラグインの実行時には以下の環境変数が設 定されます。 NINIX_PID 本体 (親プロセス) のプロセス ID NINIX_SSTP_PORT 利用可能な SSTP ポート番号 (もし無ければ 文字列 "none" が設定される) NINIX_PLUGIN_DIR プラグインが格納されているディレクトリ 本体が終了するとプラグインに対して SIGHUP シグナルが送られます。 プラグインが何をしていつ終了するかはプラグイン作成者の自由です。 プラグインと本体との通信には SSTP を用います。同一ホスト上で複数 の本体が動いている可能性がありますので、NINIX_SSTP_PORT で指定さ れた以外のポート番号は使わないようにして下さい。 (*注4) ninix-aya 4.0.7以降はzip形式のみです (*注5) ninix-ayaでの仕様拡張: plugin.txt の中で "charset" を指定することでそれ以降の行では EUC-JP 以外の文字コードの使用が可能になります。指定をしなかった 場合には EUC-JP であると仮定して処理されます。 Python 栞モジュール(*注6) ------------------- (*注6) ninix-aya 2.1.5以降削除されました 栞モジュールを shiori.py というファイル名でゴーストのアーカイブ ファイルに入れることにより、Python で栞モジュールを自作すること ができます。 shiori.py は以下のモジュール関数を公開しなければなりません。 list_dict(dir) 辞書ファイルのリストを返す。このリストで与えられたファイルの みがインストールの対象となる。リストの各要素は引数で指定され たディレクトリからの絶対パスでなければならない。 open(dir, debug=0) ninix.shiori モジュールの Shiori クラスで定義されたインター フェイスを持つクラス (Shiori クラスのサブクラス) のインスタ ンスを返す。 簡単な実装例を以下に示します。 ---------------------------------------------------------------- import ninix.shiori import time import whrandom def list_dict(dir): return [] def open(dir, debug=0): return Shiori(dir, debug) class Shiori(ninix.shiori.Shiori): def get_event_response(self, event, ref0=None, ref1=None, ref2=None, ref3=None, ref4=None, ref5=None, ref6=None, ref7=None): if event in ["OnFirstBoot", "OnBoot"]: return "%username、こんにちは。" elif event == "OnMouseDoubleClick": return whrandom.choice(["つつかないで下さい。", "何も出ません。"]) elif event == "OnMinuteChange": now = time.localtime(time.time()) if now[4] == 0: return "%d時です。" % now[3] elif now[4] == 30: return "%d時半です。" % now[3] return None def getstring(self, name): if name == "homeurl": return "http://localhost/nanika/" return None ---------------------------------------------------------------- ninix-aya-4.3.9/doc/kawari.txt000066400000000000000000000057471172114553600162320ustar00rootroot00000000000000------------------------------------------- リアル華和梨8 for ninix インストールガイド ------------------------------------------- これは何か ---------- ninix-ayaで「華和梨」ベースのゴーストを使う場合、通常はninix内部の 「華和梨」互換モジュールが使われますが、あべさんが作成されたパッチを 使うことにより本物の華和梨8を組み込んで使うことができます。 この資料では、その手順を説明します。 手順 ---- 1. ダウンロード ninix-aya本体の他に、以下のファイルをダウンロードします。 URLは、この資料の末尾にあります。 - kawari-800.lzh 華和梨本体 - kawari-800-py-gcc3.patch 華和梨にあてるパッチ(あべさん作) - kawari-800-py-module.patch 華和梨にあてるパッチ(あべさん作) 2. ninixl-ayaのインストール ninix-ayaを通常の手順でインストールします。 以下、/opt/ninix-aya の下にインストールするものとして説明しますが、 他の場所にインストールされている場合は以降の説明を適宜読み替えてください。 3. 華和梨のインストール まず華和梨のアーカイブを展開し、パッチをあてます。 % lha x kawari-800.lzh % cd kawari-800 % patch -p1 < ../kawari-800-py-gcc3.patch % patch -p1 < ../kawari-800-py-module.patch ワーキングディレクトリに移動します。 % cd build/src makeします。Linux以外のUNIXでも、linux_pymod.makをを使ってください。 freebsd_kawari.makは使えません。 makeが通らない場合、GNU makeを使うといいかもしれません。 $ make -f linux_pymod.mak kawari-800/build/mach/linux/の下に_kawari8.soファイルができているので、 これをninixがインストールされているディレクトリに移動します。 % cd ../mach/linux/ % mv _kawari8.so /opt/ninix-aya/lib/ninix/ 以上でインストール作業は完了です。 4. 動作確認 ninixを起動し、華和梨8ベースのゴーストに切り替えます。標準出力に Shiori: Real Kawari8 loader for ninix と表示されれば成功です。表示されない場合、華和梨の起動が何らかの理由で 失敗しています。この場合、ninix内蔵の互換華和梨が動作します。 連絡先 ------ この資料の著作権と文責は、 さくらのにえ 杉谷 が所有しています。ご連絡は、メールまたはwebサイト http://nie.counterghost.net/ 上のフォーラムへお願いします。 リンク ------ プログラマブル準AI 華和梨 http://kawari.sourceforge.net/ ninix に華和梨8を組み込むための手順(あべさんのサイト) http://rinakusu.tripod.co.jp/ さくらのにえ http://nie.counterghost.net/ ninix-aya開発プロジェクト http://ninix-aya.sourceforge.jp/ ninix-aya配布サイト http://sourceforge.jp/projects/ninix-aya/ 以上 ninix-aya-4.3.9/doc/saori.txt000066400000000000000000000063621172114553600160630ustar00rootroot00000000000000----------------------------- SAORI互換モジュール for ninix ----------------------------- これは何か ----------  ゴーストに機能を追加する汎用的な方法として、SAORI APIに準拠した モジュールが数多く公開されており、多くのゴーストで採用されています。 SAORIモジュールはWindowsのDLLとして提供されているため、ninix上で動作 させることはできません。そこで、ninix-ayaではSAORI互換モジュールを 搭載することにより、これらのゴーストの動作を再現しています。 設定 ---- 音声ファイル再生はGStreamerがそのファイル形式の再生に対応していれば 設定は必要ありません. それ以外のモジュールは特に設定の必要はありません.  現在、以下の互換モジュールが収録されています。 mciaudio.py ----------- MIY氏が開発されている「MCIAUDIO」と互換のモジュールです。 音声ファイルの再生が可能になります. mciaudior.py ------------ umeici氏が開発されている「MCIAUDIOR」と互換のモジュールです。 音声ファイルの再生が可能になります. bln.py ------ umeici氏が開発されている「easyballoon」と互換のモジュールです。 ゴースト独自の追加バルーンを表示できます。 textcopy.py ----------- 橋本孔明氏が開発されている「textcopy.dll」と互換のモジュールです。 文字列をクリップボードにコピーする機能を提供します。 X11のセレクションを使って実現しています。 hanayu.py --------- りゅう氏が開発されている「花柚」と互換のモジュールです。 ゴーストの過去一週間の起動時間をグラフ表示します。 wmove.py -------- tmizu氏が開発されている「wmove.dll」と互換のモジュールです。 サーフェスを移動させたり位置情報を取得する機能を提供します。 saori_cpuid.py -------------- 七瀬いーうぃ氏が開発されている「saori_cpuid.dll」と互換のモジュールです。 OS情報を取得する機能を提供します。 ssu.py ------ 櫛ケ浜やぎ氏が開発されている「ssu.dll」と互換のモジュールです。 osuwari.py -------- ukiya氏が開発されている「osuwari.dll」と互換のモジュールです。 ゴーストをウィンドウに「お座り」させる機能を提供します。 httpc.py -------- 櫛ヶ浜やぎ氏が開発されている「httpc.dll」と互換のモジュールです。 Web上の各種データをHTTPで取得する機能を提供します。 連絡先 ------ この資料の著作権と文責は、 さくらのにえ 杉谷 が所有しています。ご連絡は、メールまたはwebサイト http://nie.counterghost.net/ 上のフォーラムへお願いします。 リンク ------ SAORI collection http://members.jcom.home.ne.jp/umeici/saoricollect.html umeici氏提供の総合リンク集です。 さくらのにえ http://nie.counterghost.net/ ninix-aya開発プロジェクト http://ninix-aya.sourceforge.jp/ (当ソフトウェア配布サイト) http://sourceforge.jp/projects/ninix-aya/ 以上 ninix-aya-4.3.9/lib/000077500000000000000000000000001172114553600141775ustar00rootroot00000000000000ninix-aya-4.3.9/lib/ninix/000077500000000000000000000000001172114553600153245ustar00rootroot00000000000000ninix-aya-4.3.9/lib/ninix/__init__.py000066400000000000000000000000001172114553600174230ustar00rootroot00000000000000ninix-aya-4.3.9/lib/ninix/alias.py000066400000000000000000000066351172114553600170010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2004-2012 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. # import logging import sys import ninix.config def fatal(error): logging.error('alias.py: {0}'.format(str(error))) return ninix.config.null_config() def create_from_file(path): with open(path) as f: buf = [line.strip() for line in f if line.strip()] return create_from_buffer(buf) def create_from_buffer(buf): dic = ninix.config.Config() i, j = 0, len(buf) while i < j: line = buf[i] i += 1 if line in ['sakura.surface.alias', 'kero.surface.alias']: name = line table = {} try: while 1: if i < j: line = buf[i] i += 1 else: raise ValueError, 'unexpedted end of file' line = line.replace('\x81\x40', '').strip() if not line: continue elif line == '{': break raise ValueError, 'open brace not found' while 1: if i < j: line = buf[i] i += 1 else: raise ValueError, 'unexpected end of file' line = line.replace('\x81\x40', '').strip() if not line: continue elif line == '}': break line = line.split(',', 1) if len(line) == 2: key, values = [s.strip() for s in line] else: raise ValueError, 'malformed line found' if values and \ values.startswith('[') and values.endswith(']'): table[key] = [] for value in values[1:-1].split(','): try: value = str(int(value)) except ValueError: pass table[key].append(value) else: raise ValueError, 'malformed line found' except ValueError as error: return fatal(error) dic[name] = table else: line = line.split(',', 1) if len(line) == 2: key, value = [s.strip() for s in line] else: return fatal('malformed line found') if key == 'makoto': if value and \ value.startswith('[') and value.endswith(']'): value = value[1:-1].split(',') else: value = [value] dic[key] = value return dic # test if __name__ == '__main__': print create_from_file(sys.argv[1]) ninix-aya-4.3.9/lib/ninix/balloon.py000066400000000000000000001434521172114553600173350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import gtk import pango import cairo import glib import ninix.pix class Balloon(object): def __init__(self): self.request_parent = lambda *a: None # dummy self.synchronized = [] self.user_interaction = False self.window = [] # create communicatebox self.communicatebox = CommunicateBox() self.communicatebox.set_responsible(self.handle_request) # create teachbox self.teachbox = TeachBox() self.teachbox.set_responsible(self.handle_request) # create inputbox self.inputbox = InputBox() self.inputbox.set_responsible(self.handle_request) def set_responsible(self, request_method): self.request_parent = request_method def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { 'reset_user_interaction': self.reset_user_interaction, } handler = handlers.get(event) if handler is None: result = self.request_parent( event_type, event, *arglist, **argdict) else: result = handler(*arglist, **argdict) if event_type == 'GET': return result def reset_user_interaction(self): self.user_interaction = False def get_text_count(self, side): if len(self.window) > side: return self.window[side].get_text_count() else: return 0 def get_window(self, side): if len(self.window) > side: return self.window[side].window ## FIXME else: return None def reset_text_count(self, side): if len(self.window) > side: self.window[side].reset_text_count() def reset_balloon(self): for balloon_window in self.window: balloon_window.reset_balloon() def create_gtk_window(self, title): window = ninix.pix.TransparentWindow() window.set_focus_on_map(False) window.set_title(title) window.set_decorated(False) window.set_resizable(False) window.set_skip_pager_hint(False) window.set_skip_taskbar_hint(True) window.connect('delete_event', self.delete) window.realize() return window def identify_window(self, win): for balloon_window in self.window: if win == balloon_window.window.window: return True return False def delete(self, window, event): return True def finalize(self): for balloon_window in self.window: balloon_window.destroy() self.window = [] self.communicatebox.destroy() self.teachbox.destroy() self.inputbox.destroy() def new(self, desc, balloon): self.desc = desc self.directory = balloon['balloon_dir'][0] balloon0 = {} balloon1 = {} communicate0 = None communicate1 = None communicate2 = None communicate3 = None for key, value in balloon.items(): if key in ['arrow0', 'arrow1']: balloon0[key] = value balloon1[key] = value elif key == 'sstp': balloon0[key] = value # sstp marker elif key.startswith('s'): balloon0[key] = value # Sakura elif key.startswith('k'): balloon1[key] = value # Unyuu elif key == 'c0': communicate0 = value # send box elif key == 'c1': communicate1 = value # communicate box elif key == 'c2': communicate2 = value # teach box elif key == 'c3': communicate3 = value # input box self.balloon0 = balloon0 self.balloon1 = balloon1 # create balloon windows for balloon_window in self.window: balloon_window.destroy() self.window = [] self.add_window(0) self.add_window(1) # configure communicatebox self.communicatebox.new(desc, communicate1) # configure teachbox self.teachbox.new(desc, communicate2) # configure inputbox self.inputbox.new(desc, communicate3) def add_window(self, side): assert len(self.window) == side if side == 0: name = 'balloon.sakura' id_format = 's{0:d}' balloon = self.balloon0 elif side == 1: name = 'balloon.kero' id_format = 'k{0:d}' balloon = self.balloon1 else: name = 'balloon.char{0:d}'.format(side) id_format = 'k{0:d}' balloon = self.balloon1 gtk_window = self.create_gtk_window(name) balloon_window = BalloonWindow( gtk_window, side, self.desc, balloon, id_format) balloon_window.set_responsible(self.handle_request) self.window.append(balloon_window) def reset_fonts(self): for window in self.window: window.reset_fonts() def get_balloon_directory(self): return self.directory def get_balloon_size(self, side): if len(self.window) > side: return self.window[side].get_balloon_size() else: return (0, 0) def get_balloon_windowposition(self, side): if len(self.window) > side: return self.window[side].get_balloon_windowposition() else: return (0, 0) def set_balloon_default(self): for side in range(len(self.window)): self.window[side].set_balloon(0) ## FIXME def set_balloon(self, side, num): if len(self.window) > side: self.window[side].set_balloon(num) def set_direction(self, side, dir): if len(self.window) > side: self.window[side].set_direction(dir) def set_position(self, side, base_x, base_y): if len(self.window) > side: self.window[side].set_position(base_x, base_y) def get_position(self, side): if len(self.window) > side: return self.window[side].get_position() else: return (0, 0) def is_shown(self, side): return 0 if len(self.window) <= side else self.window[side].is_shown() def show(self, side): if len(self.window) > side: self.window[side].show() def hide_all(self): for side in range(len(self.window)): self.window[side].hide() def hide(self, side): if len(self.window) > side: self.window[side].hide() def raise_all(self): for side in range(len(self.window)): self.window[side].raise_() def raise_(self, side): if len(self.window) > side: self.window[side].raise_() def lower_all(self): for side in range(len(self.window)): self.window[side].lower() def lower(self, side): if len(self.window) > side: self.window[side].lower() def synchronize(self, list): self.synchronized = list def clear_text_all(self): for side in range(len(self.window)): self.clear_text(side) def clear_text(self, side): if self.synchronized: for side in self.synchronized: if len(self.window) > side: self.window[side].clear_text() else: if len(self.window) > side: self.window[side].clear_text() def append_text(self, side, text): if self.synchronized: for side in self.synchronized: if len(self.window) > side: self.window[side].append_text(text) else: if len(self.window) > side: self.window[side].append_text(text) def append_sstp_marker(self, side): if len(self.window) > side: self.window[side].append_sstp_marker() def append_link_in(self, side, label): if self.synchronized: for side in self.synchronized: if len(self.window) > side: self.window[side].append_link_in(label) else: if len(self.window) > side: self.window[side].append_link_in(label) def append_link_out(self, side, label, value): if self.synchronized: for side in self.synchronized: if len(self.window) > side: self.window[side].append_link_out(label, value) else: if len(self.window) > side: self.window[side].append_link_out(label, value) def append_link(self, side, label, value, newline_required=0): if self.synchronized: for side in self.synchronized: if len(self.window) > side: self.window[side].append_link_in(label) self.window[side].append_text(value) self.window[side].append_link_out(label, value) if newline_required: self.window[side].set_newline() else: if len(self.window) > side: self.window[side].append_link_in(label) self.window[side].append_text(value) self.window[side].append_link_out(label, value) if newline_required: self.window[side].set_newline() def append_meta(self, side, tag): if self.synchronized: for side in self.synchronized: if len(self.window) > side: self.window[side].append_meta(tag) else: if len(self.window) > side: self.window[side].append_meta(tag) def append_image(self, side, path, x, y): if len(self.window) > side: self.window[side].append_image(path, x, y) def show_sstp_message(self, message, sender): self.window[0].show_sstp_message(message, sender) def hide_sstp_message(self): self.window[0].hide_sstp_message() def open_communicatebox(self): if not self.user_interaction: self.user_interaction = True self.communicatebox.show() def open_teachbox(self): if not self.user_interaction: self.user_interaction = True self.request_parent('NOTIFY', 'notify_event', 'OnTeachStart') self.teachbox.show() def open_inputbox(self, symbol, limittime=-1, default=None): if not self.user_interaction: self.user_interaction = True self.inputbox.set_symbol(symbol) self.inputbox.set_limittime(limittime) self.inputbox.show(default) class BalloonWindow(object): def __init__(self, window, side, desc, balloon, id_format): self.window = window self.side = side self.request_parent = lambda *a: None # dummy self.desc = desc self.balloon = balloon self.balloon_id = None self.id_format = id_format self.num = 0 self.__shown = 0 self.sstp_marker = [] self.sstp_region = None self.sstp_message = None self.images = [] self.width = 0 self.height = 0 self.__font_name = '' self.text_count = 0 self.balloon_pixbuf = None # create drawing area self.darea = gtk.DrawingArea() self.darea.set_events(gtk.gdk.EXPOSURE_MASK| gtk.gdk.BUTTON_PRESS_MASK| gtk.gdk.POINTER_MOTION_MASK| gtk.gdk.POINTER_MOTION_HINT_MASK| gtk.gdk.SCROLL_MASK) self.darea.connect('expose_event', self.redraw) self.darea.connect('button_press_event', self.button_press) self.darea.connect('motion_notify_event', self.motion_notify) self.darea.connect('scroll_event', self.scroll) self.window.add(self.darea) self.window.realize() self.window.window.set_back_pixmap(None, False) self.layout = pango.Layout(self.darea.get_pango_context()) self.sstp_layout = pango.Layout(self.darea.get_pango_context()) mask_r = desc.get_with_type('maskcolor.r', int, 128) mask_g = desc.get_with_type('maskcolor.g', int, 128) mask_b = desc.get_with_type('maskcolor.b', int, 128) self.cursor_color = (mask_r / 255., mask_g / 255., mask_b / 255.) text_r = desc.get_with_type(['font.color.r', 'fontcolor.r'], int, 0) text_g = desc.get_with_type(['font.color.g', 'fontcolor.g'], int, 0) text_b = desc.get_with_type(['font.color.b', 'fontcolor.b'], int, 0) self.text_normal_color = (text_r / 255., text_g / 255., text_b / 255.) if desc.get_with_type('maskmethod', int) == 1: text_r = 255 - text_r text_g = 255 - text_g text_b = 255 - text_b self.text_active_color = (text_r / 255., text_g / 255., text_b / 255.) # initialize self.direction = min(side, 1) ## kluge: multi character self.position = (0, 0) self.reset_fonts() self.clear_text() def set_responsible(self, request_method): self.request_parent = request_method @property def scale(self): scalling = self.request_parent('GET', 'get_preference', 'balloon_scalling') scale = self.request_parent('GET', 'get_preference', 'surface_scale') return scale if scalling else 100 # [%] def get_balloon_windowposition(self): winpos_x = self.__get_with_scaling('windowposition.x', int, 0) winpos_y = self.__get_with_scaling('windowposition.y', int, 0) return winpos_x, winpos_y def get_pixbuf(self, balloon_id): #assert balloon_id in self.balloon try: path, config = self.balloon[balloon_id] use_pna = self.request_parent('GET', 'get_preference', 'use_pna') pixbuf = ninix.pix.create_pixbuf_from_file( path, use_pna=use_pna) except: return None scale = self.scale w = pixbuf.get_width() h = pixbuf.get_height() w = max(8, int(w * scale / 100)) h = max(8, int(h * scale / 100)) pixbuf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR) return pixbuf, (w, h) def reset_fonts(self): font_name = self.request_parent('GET', 'get_preference', 'balloon_fonts') if self.__font_name == font_name: return self.font_desc = pango.FontDescription(font_name) pango_size = self.font_desc.get_size() if pango_size == 0: default_size = 12 # for Windows environment size = self.desc.get_with_type( ['font.height', 'font.size'], int, default_size) pango_size = size * 3 / 4 # convert from Windows to GTK+ pango_size *= pango.SCALE scale = self.scale self.font_desc.set_size(pango_size * scale / 100) self.__font_name = font_name self.layout.set_font_description(self.font_desc) self.layout.set_wrap(pango.WRAP_CHAR) # XXX # font for sstp message if self.side == 0: self.sstp_font_desc = pango.FontDescription(font_name) pango_size = self.sstp_font_desc.get_size() if pango_size == 0: default_size = 10 # for Windows environment size = self.desc.get_with_type( 'sstpmessage.font.height', int, default_size) pango_size = size * 3 / 4 # convert from Windows to GTK+ pango_size *= pango.SCALE self.sstp_font_desc.set_size(pango_size * scale / 100) self.sstp_layout.set_font_description(self.sstp_font_desc) self.sstp_layout.set_wrap(pango.WRAP_CHAR) if self.balloon_id is not None: self.reset_message_regions() if self.__shown: self.darea.queue_draw() @property def alpha_channel(self): alpha = self.request_parent('GET', 'get_preference', 'balloon_alpha') if alpha is None or not 0.1 <= alpha <= 1.0: alpha = 1.0 return alpha def reset_sstp_marker(self): if self.side == 0: assert self.balloon_pixbuf is not None w, h = self.balloon_pixbuf[1] # sstp marker position self.sstp = [] x = self.config_adjust('sstpmarker.x', w, 30) y = self.config_adjust('sstpmarker.y', h, -20) self.sstp.append((x, y)) # sstp marker x = self.config_adjust('sstpmessage.x', w, 50) y = self.config_adjust('sstpmessage.y', h, -20) self.sstp.append((x, y)) # sstp message # sstp marker pixbuf (not only for self.side == 0) self.sstp_pixbuf = self.get_pixbuf('sstp') def reset_arrow(self): # arrow positions self.arrow = [] assert self.balloon_pixbuf is not None w, h = self.balloon_pixbuf[1] x = self.config_adjust('arrow0.x', w, -10) y = self.config_adjust('arrow0.y', h, 10) self.arrow.append((x, y)) x = self.config_adjust('arrow1.x', w, -10) y = self.config_adjust('arrow1.y', h, -20) self.arrow.append((x, y)) # arrow pixbufs and sizes self.arrow0_pixbuf = self.get_pixbuf('arrow0') self.arrow1_pixbuf = self.get_pixbuf('arrow1') def reset_message_regions(self): w, h = self.layout.get_pixel_size() self.font_height = h self.line_space = 1 self.layout.set_spacing(self.line_space) # font metrics origin_x = self.__get_with_scaling( 'origin.x', int, self.__get_with_scaling( 'zeropoint.x', int, self.__get_with_scaling('validrect.left', int, 14))) origin_y = self.__get_with_scaling( 'origin.y', int, self.__get_with_scaling( 'zeropoint.y', int, self.__get_with_scaling('validrect.top', int, 14))) wpx = self.__get_with_scaling( 'wordwrappoint.x', int, self.__get_with_scaling('validrect.right', int, -14)) if wpx > 0: line_width = wpx - origin_x elif wpx < 0: line_width = self.width - origin_x + wpx else: line_width = self.width - origin_x * 2 wpy = self.__get_with_scaling('validrect.bottom', int, -14) if wpy > 0: text_height = min(wpy, self.height) - origin_y elif wpy < 0: text_height = self.height - origin_y + wpy else: text_height = self.height - origin_y * 2 line_height = self.font_height + self.line_space self.lines = text_height / line_height self.line_regions = [] y = origin_y for _ in range(self.lines + 1): self.line_regions.append((origin_x, y, line_width, line_height)) y = y + line_height self.line_width = line_width # sstp message region if self.side == 0: w, h = self.sstp_layout.get_pixel_size() x, y = self.sstp[1] w = line_width + origin_x - x self.sstp_region = (x, y, w, h) def update_line_regions(self, offset, new_y): origin_y = self.__get_with_scaling( 'origin.y', int, self.__get_with_scaling( 'zeropoint.y', int, self.__get_with_scaling('validrect.top', int, 14))) wpy = self.__get_with_scaling('validrect.bottom', int, -14) if wpy > 0: text_height = min(wpy, self.height) - origin_y elif wpy < 0: text_height = self.height - origin_y + wpy else: text_height = self.height - origin_y * 2 line_height = self.font_height + self.line_space origin_x, y, line_width, line_height = self.line_regions[offset] self.lines = offset + (text_height - new_y) / line_height y = new_y for i in range(offset, len(self.line_regions)): self.line_regions[i] = (origin_x, y, line_width, line_height) y += line_height for i in range(len(self.line_regions), self.lines + 1): self.line_regions.append((origin_x, y, line_width, line_height)) y += line_height def get_balloon_size(self): return (self.width, self.height) def reset_balloon(self): self.set_balloon(self.num) def set_balloon(self, num): self.num = num balloon_id = self.id_format.format(num * 2 + self.direction) self.balloon_pixbuf = self.get_pixbuf(balloon_id) if self.balloon_pixbuf is None: balloon_id = self.id_format.format(0 + self.direction) self.balloon_pixbuf = self.get_pixbuf(balloon_id) assert self.balloon_pixbuf is not None self.balloon_id = balloon_id # change pixbuf and window position x, y = self.position self.width, self.height = self.balloon_pixbuf[1] self.darea.set_size_request(self.width, self.height) mask = gtk.gdk.Pixmap(None, self.width, self.height, 1) self.balloon_pixbuf[0].render_threshold_alpha( mask, 0, 0, 0, 0, self.width, self.height, 1) self.window.mask = mask self.reset_arrow() self.reset_sstp_marker() self.reset_message_regions() if self.__shown: self.request_parent('NOTIFY', 'position_balloons') self.darea.queue_draw() def set_direction(self, dir): if self.direction != dir: self.direction = dir # 0: left, 1: right self.reset_balloon() def config_adjust(self, name, base, default_value): path, config = self.balloon[self.balloon_id] value = config.get_with_type(name, int) if value is None: value = self.desc.get_with_type(name, int) if value is None: value = default_value if value < 0: value = base + value return int(value * self.scale / 100) def __get_with_scaling(self, name, conv, default_value): path, config = self.balloon[self.balloon_id] value = config.get_with_type(name, conv) if value is None: value = self.desc.get_with_type(name, conv) if value is None: value = default_value return conv(value * self.scale / 100) def __move(self): x, y = self.get_position() self.window.resize_move(x, y) def set_position(self, base_x, base_y): if self.balloon_id is None: ## FIXME return px, py = self.get_balloon_windowposition() if self.direction == 0: w, h = self.get_balloon_size() x = base_x + px - w else: x = base_x + px y = base_y + py self.position = (x, y) if self.__shown: self.__move() def get_position(self): return self.position def destroy(self, finalize=0): self.window.remove(self.darea) self.darea.destroy() self.window.destroy() def is_shown(self): return 1 if self.__shown else 0 def show(self): if self.__shown: return self.__shown = 1 self.darea.show() self.window.show() # make sure window is in its position self.__move() self.raise_() def hide(self): if not self.__shown: return self.window.hide() self.__shown = 0 self.images = [] def raise_(self): if self.__shown: self.window.window.raise_() def lower(self): if self.__shown: self.window.window.lower() def show_sstp_message(self, message, sender): if self.sstp_region is None: self.show() self.sstp_message = u'{0} ({1})'.format(message, sender) x, y, w, h = self.sstp_region self.sstp_layout.set_text(self.sstp_message) message_width, message_height = self.sstp_layout.get_pixel_size() if message_width > w: self.sstp_message = u'... ({0})'.format(sender) i = 0 while 1: i += 1 s = u'{0}... ({1})'.format(message[:i], sender) self.sstp_layout.set_text(s) message_width, message_height = \ self.sstp_layout.get_pixel_size() if message_width > w: break self.sstp_message = s self.darea.queue_draw() def hide_sstp_message(self): self.sstp_message = None self.darea.queue_draw() def redraw_sstp_message(self): if self.sstp_message is None: return cr = self.darea.window.cairo_create() # draw sstp marker if self.sstp_pixbuf is not None: x, y = self.sstp[0] cr.set_source_pixbuf(self.sstp_pixbuf[0], x, y) cr.paint() # draw sstp message x, y, w, h = self.sstp_region self.sstp_layout.set_text(self.sstp_message) cr.set_source_rgba(*self.text_normal_color) cr.move_to(x, y) cr.show_layout(self.sstp_layout) del cr def redraw_arrow0(self): if self.lineno <= 0: return cr = self.darea.window.cairo_create() x, y = self.arrow[0] cr.set_source_pixbuf(self.arrow0_pixbuf[0], x, y) cr.paint() del cr def redraw_arrow1(self): if self.lineno + self.lines >= len(self.text_buffer): return cr = self.darea.window.cairo_create() x, y = self.arrow[1] cr.set_source_pixbuf(self.arrow1_pixbuf[0], x, y) cr.paint() del cr def set_markup(self, index, text): tags_ = ('sup', 'sub', 's', 'u') count_ = {} for tag_ in tags_: count_[tag_] = 0 markup_list = [] for sl, sn, tag in self.meta_buffer: if sl == index: markup_list.append((sn, tag)) if not markup_list: return glib.markup_escape_text(text) markup_list.sort() markup_list.reverse() pn = len(text) for sn, tag in markup_list: text = ''.join((text[:sn], tag, glib.markup_escape_text(text[sn:pn]), text[pn:])) pn = sn if tag[1] == '/': tag_ = tag[2:-1] assert tag_ in tags_ count_[tag_] -= 1 if count_[tag_] < 0: text = ''.join(('<', tag_, '>', text)) count_[tag_] += 1 else: tag_ = tag[1:-1] assert tag_ in tags_ count_[tag_] += 1 if count_[tag_] > 0: text = ''.join((text, '')) count_[tag_] -= 1 return text def redraw(self, darea, event): if not self.__shown: return True assert self.balloon_pixbuf is not None cr = darea.window.cairo_create() cr.save() cr.set_operator(cairo.OPERATOR_CLEAR) cr.paint() cr.restore() cr.set_source_pixbuf(self.balloon_pixbuf[0], 0, 0) cr.paint_with_alpha(self.alpha_channel) # draw images for i in range(len(self.images)): pixbuf, (w, h), (x, y) = self.images[i] scale = self.scale w = max(8, int(w * scale / 100)) h = max(8, int(h * scale / 100)) if x == 'centerx': bw, bh = self.get_balloon_size() x = (bw - w) / 2 else: try: x = int(x) except: continue if y == 'centery': bw, bh = self.get_balloon_size() y = (bh - h) / 2 else: try: y = int(y) except: continue pixbuf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR) cr.set_source_pixbuf(pixbuf, x, y) cr.paint_with_alpha(self.alpha_channel) # draw text i = self.lineno j = len(self.text_buffer) line = 0 while line < self.lines: if i >= j: break x, y, w, h = self.line_regions[line] if self.text_buffer[i].endswith('\n[half]'): new_y = int(y + (self.font_height + self.line_space) / 2) markup = self.set_markup(i, self.text_buffer[i][:-7]) else: new_y = int(y + self.font_height + self.line_space) markup = self.set_markup(i, self.text_buffer[i]) self.update_line_regions(line + 1, new_y) self.layout.set_markup(markup) cr.set_source_rgba(*self.text_normal_color) cr.move_to(x, y) cr.show_layout(self.layout) if self.sstp_pixbuf is not None: for l, c in self.sstp_marker: if l == i: mw, mh = self.sstp_pixbuf[1] self.layout.set_text(self.text_buffer[i][:c]) text_w, text_h = self.layout.get_pixel_size() mx = x + text_w my = y + (self.font_height + self.line_space) / 2 my = my - mh / 2 cr.set_source_pixbuf(self.sstp_pixbuf[0], mx, my) cr.paint_with_alpha(self.alpha_channel) i += 1 line += 1 del cr if self.side == 0 and self.sstp_message: self.redraw_sstp_message() if self.selection is not None: self.update_link_region(darea, self.selection) self.redraw_arrow0() self.redraw_arrow1() return False def update_link_region(self, darea, index): cr = darea.window.cairo_create() sl = self.link_buffer[index][0] el = self.link_buffer[index][2] if self.lineno <= sl <= self.lineno + self.lines: sn = self.link_buffer[index][1] en = self.link_buffer[index][3] for n in range(sl, el + 1): if n - self.lineno >= len(self.line_regions): break x, y, w, h = self.line_regions[n - self.lineno] if sl == el: markup = self.set_markup(n, self.text_buffer[n][:sn]) self.layout.set_markup(markup) text_w, text_h = self.layout.get_pixel_size() x += text_w markup = self.set_markup(n, self.text_buffer[n][sn:en]) self.layout.set_markup(markup) text_w, text_h = self.layout.get_pixel_size() w = text_w start = sn end = en elif n == sl: markup = self.set_markup(n, self.text_buffer[n][:sn]) self.layout.set_markup(markup) text_w, text_h = self.layout.get_pixel_size() x += text_w markup = self.set_markup(n, self.text_buffer[n][sn:]) self.layout.set_markup(markup) text_w, text_h = self.layout.get_pixel_size() w = text_w start = sn end = len(self.text_buffer[n]) elif n == el: markup = self.set_markup(n, self.text_buffer[n][:en]) self.layout.set_markup(markup) text_w, text_h = self.layout.get_pixel_size() w = text_w start = 0 end = en else: markup = self.set_markup(n, self.text_buffer[n]) self.layout.set_markup(markup) text_w, text_h = self.layout.get_pixel_size() w = text_w start = 0 end = len(self.text_buffer[n]) markup = self.set_markup(n, self.text_buffer[n][start:end]) self.layout.set_markup(markup) cr.set_source_rgba(*self.cursor_color) cr.rectangle(x, y, w, h) cr.fill() cr.move_to(x, y) cr.set_source_rgba(*self.text_active_color) cr.show_layout(self.layout) del cr def check_link_region(self, px, py): new_selection = None for i in range(len(self.link_buffer)): sl = self.link_buffer[i][0] el = self.link_buffer[i][2] if self.lineno <= sl <= self.lineno + self.lines: sn = self.link_buffer[i][1] en = self.link_buffer[i][3] for n in range(sl,el + 1): if n - self.lineno >= len(self.line_regions): break x, y, w, h = self.line_regions[n - self.lineno] if n == sl: markup = self.set_markup(n, self.text_buffer[n][:sn]) self.layout.set_markup(markup) text_w, text_h = self.layout.get_pixel_size() x += text_w if n == sl and n == el: markup = self.set_markup(n, self.text_buffer[n][sn:en]) elif n == el: markup = self.set_markup(n, self.text_buffer[n][:en]) else: markup = self.set_markup(n, self.text_buffer[n]) self.layout.set_markup(markup) text_w, text_h = self.layout.get_pixel_size() w = text_w if x <= px < x + w and y <= py < y + h: new_selection = i break if new_selection is not None: if self.selection != new_selection: sl, sn, el, en, link_id, raw_text, text = \ self.link_buffer[new_selection] self.request_parent( 'NOTIFY', 'notify_event', 'OnChoiceEnter', raw_text, link_id, self.selection) else: if self.selection is not None: self.request_parent('NOTIFY', 'notify_event', 'OnChoiceEnter') if new_selection == self.selection: return 0 else: self.selection = new_selection return 1 # dirty flag def motion_notify(self, darea, event): if event.is_hint: x, y, state = self.darea.window.get_pointer() else: x, y, state = event.x, event.y, event.state if not self.link_buffer: return True px = int(x) py = int(y) if self.check_link_region(px, py): self.darea.queue_draw() return True def scroll(self, darea, event): px = int(event.x) py = int(event.y) if event.direction == gtk.gdk.SCROLL_UP: if self.lineno > 0: self.lineno = max(self.lineno - 2, 0) self.check_link_region(px, py) self.darea.queue_draw() elif event.direction == gtk.gdk.SCROLL_DOWN: if self.lineno + self.lines < len(self.text_buffer): self.lineno = min(self.lineno + 2, len(self.text_buffer) - self.lines) self.check_link_region(px, py) self.darea.queue_draw() return True def button_press(self, darea, event): self.request_parent('NOTIFY', 'reset_idle_time') click = 1 if event.type == gtk.gdk.BUTTON_PRESS else 2 if self.request_parent('GET', 'is_paused'): self.request_parent('NOTIFY', 'notify_balloon_click', event.button, click, self.side) return True # arrows px = int(event.x) py = int(event.y) # up arrow w, h = self.arrow0_pixbuf[1] x, y = self.arrow[0] if x <= px <= x + w and y <= py <= y + h: if self.lineno > 0: self.lineno = max(self.lineno - 2, 0) self.darea.queue_draw() return True # down arrow w, h = self.arrow1_pixbuf[1] x, y = self.arrow[1] if x <= px <= x + w and y <= py <= y + h: if self.lineno + self.lines < len(self.text_buffer): self.lineno = min(self.lineno + 2, len(self.text_buffer) - self.lines) self.darea.queue_draw() return True # links if self.selection is not None: sl, sn, el, en, link_id, raw_text, text = \ self.link_buffer[self.selection] self.request_parent('NOTIFY', 'notify_link_selection', link_id, raw_text, self.selection) return True # balloon's background self.request_parent('NOTIFY', 'notify_balloon_click', event.button, click, self.side) return True def clear_text(self): self.selection = None self.lineno = 0 self.text_buffer = [] self.meta_buffer = [] self.link_buffer = [] self.newline_required = 0 self.images = [] self.sstp_marker = [] self.darea.queue_draw() def get_text_count(self): return self.text_count def reset_text_count(self): self.text_count = 0 def set_newline(self): self.newline_required = 1 def append_text(self, text): if not self.text_buffer: s = '' column = 0 index = 0 elif self.newline_required: s = '' column = 0 self.newline_required = 0 index = len(self.text_buffer) else: index = len(self.text_buffer) - 1 s = self.text_buffer.pop(-1) column = len(s) i = len(s) text = ''.join((s, text)) j = len(text) self.text_count += j p = 0 while 1: if i >= j: self.text_buffer.append(text[p:i]) self.draw_last_line(column) break if text[i] == '\n': if j >= i + 7 and text[i:i + 7] == '\n[half]': self.text_buffer.append(''.join((text[p:i], '\n[half]'))) p = i = i + 7 else: self.text_buffer.append(text[p:i]) p = i = i + 1 self.draw_last_line(column) column = 0 continue n = i + 1 if not self.__shown: self.show() markup = self.set_markup(index, text[p:n]) self.layout.set_markup(markup) text_width, text_height = self.layout.get_pixel_size() if text_width > self.line_width: self.text_buffer.append(text[p:i]) self.draw_last_line(column) column = 0 p = i i = n def append_sstp_marker(self): if self.sstp_pixbuf is None: return if not self.text_buffer: line = 0 offset = 0 else: line = len(self.text_buffer) - 1 offset = len(self.text_buffer[-1]) if self.newline_required: line = line + 1 offset = 0 self.sstp_marker.append((line, offset)) w, h = self.sstp_pixbuf[1] i = 1 while 1: space = u'\u3000' * i ## FIXME self.layout.set_text(space) text_w, text_h = self.layout.get_pixel_size() if text_w > w: break else: i += 1 self.append_text(space) self.draw_last_line(offset) def append_link_in(self, link_id): if not self.text_buffer: sl = 0 sn = 0 else: sl = len(self.text_buffer) - 1 sn = len(self.text_buffer[-1]) self.link_buffer.append((sl, sn, sl, sn, link_id, '', '')) def append_link_out(self, link_id, text): if not text: return raw_text = text if not self.text_buffer: el = 0 en = 0 else: el = len(self.text_buffer) - 1 en = len(self.text_buffer[-1]) for i in range(len(self.link_buffer)): if self.link_buffer[i][4] == link_id: sl = self.link_buffer[i][0] sn = self.link_buffer[i][1] self.link_buffer.pop(i) self.link_buffer.insert( i, (sl, sn, el, en, link_id, raw_text, text)) break def append_meta(self, tag): if not tag: return if not self.text_buffer: sl = 0 sn = 0 else: sl = len(self.text_buffer) - 1 sn = len(self.text_buffer[-1]) self.meta_buffer.append((sl, sn, tag)) def append_image(self, path, x, y): try: pixbuf = ninix.pix.create_pixbuf_from_file(path) except: return self.show() w = pixbuf.get_width() h = pixbuf.get_height() self.images.append((pixbuf, (w, h), (x, y))) self.darea.queue_draw() def draw_last_line(self, column=0): if not self.__shown: return line = len(self.text_buffer) - 1 if self.lineno <= line < self.lineno + self.lines: x, y, w, h = self.line_regions[line - self.lineno] if self.text_buffer[line].endswith('\n[half]'): offset = line - self.lineno + 1 new_y = int(y + (self.font_height + self.line_space) / 2) self.update_line_regions(offset, new_y) else: self.darea.queue_draw() if self.sstp_pixbuf is not None: for l, c in self.sstp_marker: if l == line: mw, mh = self.sstp_pixbuf[1] self.layout.set_text(self.text_buffer[l][:c]) text_w, text_h = self.layout.get_pixel_size() mx = x + text_w my = y + (self.font_height + self.line_space) / 2 my = my - mh / 2 cr = self.darea.window.cairo_create() cr.set_source_pixbuf(self.sstp_pixbuf[0], mx, my) cr.paint() del cr else: self.darea.queue_draw() while line >= self.lineno + self.lines: self.lineno += 1 self.darea.queue_draw() class CommunicateWindow(object): NAME = '' ENTRY = '' def __init__(self): self.request_parent = lambda *a: None # dummy self.window = None def set_responsible(self, request_method): self.request_parent = request_method def new(self, desc, balloon): if self.window: self.window.destroy() self.window = gtk.Window() self.window.set_title('communicate') self.window.set_decorated(False) self.window.set_resizable(False) self.window.connect('delete_event', self.delete) self.window.connect('key_press_event', self.key_press) self.window.connect('button_press_event', self.button_press) self.window.set_events(gtk.gdk.BUTTON_PRESS_MASK) self.window.set_modal(True) self.window.set_position(gtk.WIN_POS_CENTER) self.window.realize() w = desc.get_with_type('communicatebox.width', int, 250) h = desc.get_with_type('communicatebox.height', int, -1) self.entry = gtk.Entry() self.entry.connect('activate', self.activate) self.entry.set_size_request(w, h) self.entry.show() pixbuf = None if balloon: path, config = balloon # load pixbuf try: pixbuf = ninix.pix.create_pixbuf_from_file(path) except: pixbuf = None if pixbuf is not None: darea = gtk.DrawingArea() darea.set_events(gtk.gdk.EXPOSURE_MASK) darea.connect('expose_event', self.redraw, pixbuf) darea.show() x = desc.get_with_type('communicatebox.x', int, 10) y = desc.get_with_type('communicatebox.y', int, 20) fixed = gtk.Fixed() fixed.put(darea, 0, 0) fixed.put(self.entry, x, y) fixed.show() self.window.add(fixed) w = pixbuf.get_width() h = pixbuf.get_height() darea.set_size_request(w, h) mask = gtk.gdk.Pixmap(None, w, h, 1) pixbuf.render_threshold_alpha(mask, 0, 0, 0, 0, w, h, 1) self.window.shape_combine_mask(mask, 0, 0) else: box = gtk.HBox(spacing=10) box.set_border_width(10) if self.ENTRY: label = gtk.Label(self.ENTRY) box.pack_start(label, False) label.show() box.pack_start(self.entry) self.window.add(box) box.show() def redraw(self, darea, event, pixbuf): cr = darea.window.cairo_create() cr.save() cr.set_operator(cairo.OPERATOR_CLEAR) cr.paint() cr.restore() cr.set_source_pixbuf(pixbuf, 0, 0) cr.paint() del cr def destroy(self): if self.window: self.window.destroy() self.window = None def delete(self, widget, event): self.window.hide() self.cancel() return True def key_press(self, widget, event): if event.keyval == gtk.keysyms.Escape: self.window.hide() self.cancel() return True return False def button_press(self, widget, event): if event.button in [1, 2]: self.window.begin_move_drag( event.button, int(event.x_root), int(event.y_root), gtk.get_current_event_time()) return True def activate(self, widget): self.window.hide() self.enter() return True def show(self, default=''): self.entry.set_text(default) self.window.show() def enter(self): pass def cancel(self): pass class CommunicateBox(CommunicateWindow): NAME = 'communicatebox' ENTRY = 'Communicate' def new(self, desc, balloon): CommunicateWindow.new(self, desc, balloon) self.window.set_modal(False) def delete(self, widget, event): self.window.hide() self.cancel() self.request_parent('NOTIFY', 'reset_user_interaction') return True def key_press(self, widget, event): if event.keyval == gtk.keysyms.Escape: self.window.hide() self.cancel() self.request_parent('NOTIFY', 'reset_user_interaction') return True return False def activate(self, widget): self.enter() self.entry.set_text('') return True def enter(self): self.send(self.entry.get_text()) def cancel(self): self.send(None) def send(self, data): if data: data = unicode(data, 'utf-8') if data is not None: self.request_parent('NOTIFY', 'notify_event', 'OnCommunicate', 'user', data) class TeachBox(CommunicateWindow): NAME = 'teachbox' ENTRY = 'Teach' def enter(self): self.send(self.entry.get_text()) def cancel(self): self.send(None) def send(self, data): if data: data = unicode(data, 'utf-8') self.request_parent('NOTIFY', 'notify_user_teach', data) self.request_parent('NOTIFY', 'reset_user_interaction') class InputBox(CommunicateWindow): NAME = 'inputbox' ENTRY = 'Input' def new(self, desc, balloon): CommunicateWindow.new(self, desc, balloon) self.symbol = None self.limittime = -1 def set_symbol(self, symbol): self.symbol = symbol def set_limittime(self, limittime): try: limittime = int(limittime) except ValueError: limittime = -1 self.limittime = limittime def show(self, default): if default is not None: try: text = unicode(default).encode('utf-8') except: text = '' else: text = '' if int(self.limittime) < 0: self.timeout_id = None else: self.timeout_id = glib.timeout_add(self.limittime, self.timeout) CommunicateWindow.show(self, text) def timeout(self): self.window.hide() self.send('timeout') def enter(self): self.send(self.entry.get_text()) def cancel(self): self.send(None) def send(self, data): if self.timeout_id is not None: glib.source_remove(self.timeout_id) if data: data = unicode(data, 'utf-8') if data is None: data = '' ## CHECK: symbol if self.symbol == 'OnUserInput' and \ self.request_parent('GET', 'notify_event', 'OnUserInput', data): pass elif self.request_parent('GET', 'notify_event', 'OnUserInput', self.symbol, data): pass elif self.request_parent('GET', 'notify_event', self.symbol, data): pass self.request_parent('NOTIFY', 'reset_user_interaction') def test(): pass if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/communicate.py000066400000000000000000000035531172114553600202100ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # communicate.py - ghost-to-ghost communication mechanism # Copyright (C) 2002-2012 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. # class Communicate(object): def __init__(self): self.__ghosts = {} def rebuild_ghostdb(self, sakura, name='', s0=0, s1=10): if sakura in self.__ghosts: del self.__ghosts[sakura] if name is None: return else: self.__ghosts[sakura] = (name, s0, s1) def get_otherghostname(self, name): return [chr(1).join(value) for value in self.__ghosts.values() \ if value[0] != name] def send_message(self, name, sender, sentence): if name == '__SYSTEM_ALL_GHOST__': for sakura in self.__ghosts.keys(): sakura.enqueue_event('OnCommunicate', sender, sentence) elif chr(1) in name: to = name.split(chr(1)) for sakura, value in self.__ghosts.items(): if value[0] in to: sakura.enqueue_event('OnCommunicate', sender, sentence) to.remove(value[0]) if not to: break else: for sakura, value in self.__ghosts.items(): if value[0] == name: sakura.enqueue_event('OnCommunicate', sender, sentence) break ninix-aya-4.3.9/lib/ninix/config.py000066400000000000000000000045311172114553600171460ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2003-2012 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. # import codecs import logging class Config(dict): def get_with_type(self, name, conv, default=None): value = self.get(name) if value is None: return default ##assert conv is not None try: return conv(value) except ValueError: return default # XXX def get(self, name, default=None): keylist = name if isinstance(name, list) else [name] for key in keylist: if key in self: return self[key] return default def __str__(self): return ''.join( ['{0},{1}\n'.format(key.encode('utf-8'), value.encode('utf-8')) \ for key, value in self.items()]) def create_from_file(path): charset = 'Shift_JIS' # default with open(path) as f: if f.read(3) == codecs.BOM_UTF8: charset = 'UTF-8' else: f.seek(0) # rewind buf = [line.strip() for line in f if line.strip()] return create_from_buffer(buf, charset) def create_from_buffer(buf, charset='Shift_JIS'): dic = Config() for line in buf: try: key, value = line.split(',', 1) except ValueError: continue key = key.strip() if key == 'charset': value = value.strip() try: codecs.lookup(value) except: logging.error('Unsupported charset {0}'.format(value)) else: charset = value elif key in ['refreshundeletemask', 'icon', 'cursor', 'shiori', 'makoto']: dic[key] = value.strip() else: dic[key] = unicode(value, charset, 'replace').strip() return dic def null_config(): return Config() ninix-aya-4.3.9/lib/ninix/dll.py000066400000000000000000000123071172114553600164540ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # dll.py - a pseudo DLL (SHIORI/SAORI API support) module for ninix # Copyright (C) 2002-2012 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. # import sys import imp import logging import os import codecs def get_path(): # XXX import ninix return os.path.join(ninix.__path__[0], 'dll') class SAORI(object): 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 __init__(self): self.loaded = 0 def check_import(self): return 1 def load(self, dir=os.curdir): self.dir = dir result = 0 if not self.check_import(): pass elif self.loaded: result = 2 else: if self.setup(): self.loaded = 1 result = 1 return result def setup(self): return 1 def unload(self): if self.loaded == 0: return 0 else: self.loaded = 0 return self.finalize() def finalize(self): return 1 def request(self, req): req_type, argument = self.evaluate_request(req) if not req_type: return self.RESPONSE[400] elif req_type == 'GET Version': return self.RESPONSE[204] elif req_type == 'EXECUTE': result = self.execute(argument) return self.RESPONSE[204] if result is None else result else: return self.RESPONSE[400] def execute(self, args): return None def evaluate_request(self, req): req_type = None argument = [] self.charset = 'Shift_JIS' # default for line in req.splitlines(): line = line.strip() if not line: continue if req_type is None: for request in ['EXECUTE', 'GET Version']: ## FIXME if line.startswith(request): req_type = request continue if ':' not in line: continue key, value = line.split(':', 1) key = key.strip() if key == 'Charset': charset = value.strip() try: codecs.lookup(charset) except: logging.warning( 'Unsupported charset {0}'.format(repr(charset))) else: self.charset = charset if key.startswith('Argument'): ## FIXME argument.append(value) else: continue argument = [unicode(value, self.charset, 'ignore').strip() for value in argument] return req_type, argument class Library(object): def __init__(self, dll_type, sakura=None, saori_lib=None): self.__type = dll_type self.__sakura = sakura self.__saori_lib = saori_lib def request(self, name): if self.__type == 'shiori': dll_name, name = name if not name and dll_name: name = dll_name name = name.replace('\\', '/') head, tail = os.path.split(name) name = tail if not name: return None if name.lower().endswith('.dll'): # XXX name = name[:-4] module = self.__import_module(name) if not module: return None instance = None if self.__type == 'saori': if getattr(module, 'Saori', None): saori = module.Saori() if getattr(saori, 'need_ghost_backdoor', None): saori.need_ghost_backdoor(self.__sakura) else: saori = None instance = saori elif self.__type == 'shiori': if getattr(module, 'Shiori', None): shiori = module.Shiori(dll_name) if getattr(shiori, 'use_saori', None): shiori.use_saori(self.__saori_lib) else: shiori = None instance = shiori if instance is None: del module ## this is NOT proper: infects the working ghost(s). ##del sys.modules[name] return instance def __import_module(self, name): fp = None try: return sys.modules[name] except: pass try: path = get_path() fp, pathname, description = imp.find_module(name, [path]) except: return None try: return imp.load_module(name, fp, pathname, description) finally: if fp: fp.close() return None ninix-aya-4.3.9/lib/ninix/dll/000077500000000000000000000000001172114553600160775ustar00rootroot00000000000000ninix-aya-4.3.9/lib/ninix/dll/aya.py000066400000000000000000004147441172114553600172410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # aya.py - an aya.dll compatible Shiori module for ninix # Copyright (C) 2002-2012 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. # import os import sys import logging import random import time import math import shutil import re import ninix.lock from ninix.home import get_normalized_path class AyaError(ValueError): pass def encrypt_char(char): c = ord(char) j = 0 while j < 3: msb = c & 0x80 c <<= 1 c &= 0xff if msb: c |= 0x01 else: c &= 0xfe j += 1 c ^= 0xd2 return chr(c) def decrypt_char(char): c = ord(char) c ^= 0xd2 j = 0 while j < 3: lsb = c & 0x01 c >>= 1 if lsb: c |= 0x80 else: c &= 0x7f j += 1 return chr(c) def decrypt_readline(f): line = '' while 1: c = f.read(1) if c == '': break line = ''.join((line, decrypt_char(c))) if line.endswith(chr(10)) or \ line.endswith(''.join((chr(13), chr(10)))): break return line def line_strip(line): line = line.strip() while line: if line.startswith(u' '.encode('EUC-JP')): ## FIXME line = line[2:].strip() else: break while line: if line.endswith(u' '.encode('EUC-JP')): ## FIXME line = line[:-2].strip() else: break return line def find_not_quoted(line, token): position = 0 while 1: pos_new = line.find(token, position) if pos_new < 0: break elif pos_new == 0: break position = line.find('"', position) if 0 <= position < pos_new: position += 1 while position < len(line) - 1: if line[position] == '"': position += 1 break else: position += 1 continue else: break return pos_new def find_comment(line): if line.startswith('//'): return 0, len(line) start = len(line) # not len(line) - 1 end = -1 for token in [' //', '\t//', u' //'.encode('EUC-JP'), '/*']: ## FIXME pos_new = find_not_quoted(line, token) if 0 <= pos_new < start: start = pos_new if token == '/*': end = find_not_quoted(line, '*/') if end >= 0: end += 2 else: end = len(line) if start == len(line): start = -1 return start, end def get_aya_version(filelist): if not filelist: return 0 dic_files = filelist for filename in dic_files: if filename.lower().endswith('_shiori3.dic'): # XXX with open(filename) as f: for line in f: try: ## FIXME line = unicode(line, 'Shift_JIS').encode('EUC-JP', 'ignore') if line.find(u'for 文 version 4'.encode('EUC-JP')) > 0: return 4 elif line.find('for AYA5') > 0: return 5 except: return 5 else: return 3 def find_dict(aya_dir, f): comment = 0 dic_files = [] for line in f: line = unicode(line, 'Shift_JIS', 'ignore').encode('EUC-JP', 'ignore') if comment: end = find_not_quoted(line, '*/') if end < 0: continue else: line = line[end + 2:] comment = 0 while 1: start, end = find_comment(line) if start < 0: break if end < 0: comment = 1 line = line[:start] break line = ' '.join((line[:start], line[end:])) line = line_strip(line) if not line: continue if ',' not in line: continue key, value = [line_strip(x) for x in line.split(',', 1)] if key == 'dic': filename = get_normalized_path(value, encode=0) path = os.path.join(aya_dir, filename) dic_files.append(path) return dic_files def check_version(top_dir, dll_name): filename = None if os.path.isfile(os.path.join(top_dir, 'aya.txt')): filename = os.path.join(top_dir, 'aya.txt') elif os.path.isfile(os.path.join(top_dir, 'yaya.txt')): return 6 # XXX: YAYA elif dll_name is not None and \ os.path.isfile(os.path.join(top_dir, ''.join((dll_name[:-3], 'txt')))): filename = os.path.join(top_dir, ''.join((dll_name[:-3], 'txt'))) if filename is not None: with open(filename) as f: version = get_aya_version(find_dict(top_dir, f)) else: version = 0 return version class Shiori(object): __AYA_TXT = 'aya.txt' __DBNAME = 'aya_variable.cfg' def __init__(self, dll_name): self.dll_name = dll_name if dll_name is not None: self.__AYA_TXT = ''.join((dll_name[:-3], 'txt')) self.__DBNAME = ''.join((dll_name[:-4], '_variable.cfg')) self.saori = None self.dic_files = [] def use_saori(self, saori): self.saori = saori def find(self, top_dir, dll_name): result = 0 version = check_version(top_dir, dll_name) if version in [3, 4]: result = 300 return result def show_description(self): logging.info( 'Shiori: AYA compatible module for ninix\n' ' Copyright (C) 2002-2012 by Shyouzou Sugitani\n' ' Copyright (C) 2002, 2003 by MATSUMURA Namihiko') def reset(self): self.boot_time = time.time() self.aitalk = 0 self.first_boot = 0 self.dic_files = [] self.dic = AyaDictionary(self) self.global_namespace = AyaGlobalNamespace(self) self.system_functions = AyaSystemFunctions(self) self.logfile = None self.filelist = {} self.reset_request() self.ver_3 = 0 # Ver.3 def reset_request(self): self.req_command = '' self.req_protocol = '' self.req_key = [] self.req_header = {} self.global_namespace.reset_res_reference() def load(self, aya_dir=None): self.aya_dir = aya_dir self.dbpath = os.path.join(self.aya_dir, self.__DBNAME) self.saori_library = AyaSaoriLibrary(self.saori, self.aya_dir) self.reset() self.first_boot = self.global_namespace.load_database(self) try: path = os.path.join(self.aya_dir, self.__AYA_TXT) with open(path) as aya_txt: self.load_aya_txt(aya_txt) except IOError: logging.debug('cannot read aya.txt') return 0 except AyaError as error: logging.debug(error) return 0 # default setting if not self.global_namespace.exists('log'): self.global_namespace.put('log', '') if not self.global_namespace.exists('logmode'): self.global_namespace.put('logmode', 'simple') for path in self.dic_files: basename, ext = os.path.splitext(path) ext = ext.lower() if ext == '.ayc': encrypted = 1 else: encrypted = 0 try: with open(path, 'r') as dicfile: self.dic.load(dicfile, encrypted) except: logging.debug('cannnot read {0}'.format(path)) continue if not self.global_namespace.exists('aitalkinterval'): # Ver.3 self.global_namespace.put('aitalkinterval', 180) if not self.global_namespace.exists('securitylevel'): # Ver.3 self.global_namespace.put('securitylevel', 'high') if not self.dic.get_function('OnRequest'): # Ver.3 self.ver_3 = 1 self.request('NOTIFY SHIORI/3.0\r\n' \ 'ID: OnLoad\r\n' \ 'Sender: AYA\r\n' \ 'SecurityLevel: local\r\n' \ 'Path: {0}\r\n\r\n'.format( unicode(self.aya_dir.replace('/', '\\'), 'EUC-JP', 'ignore').encode('Shift_JIS', 'ignore'))) ## FIXME: UTF-8(path) -> Shift_JIS return 1 def load_aya_txt(self, f): comment = 0 for line in f: line = unicode(line, 'Shift_JIS', 'ignore').encode('EUC-JP', 'ignore') ## FIXME if comment: end = find_not_quoted(line, '*/') if end < 0: continue else: line = line[end + 2:] comment = 0 while 1: start, end = find_comment(line) if start < 0: break if end < 0: comment = 1 line = line[:start] break line = ' '.join((line[:start], line[end:])) line = line_strip(line) if not line: continue if ',' not in line: continue key, value = [line_strip(x) for x in line.split(',', 1)] self.evaluate_config(key, value) def evaluate_config(self, key, value): if key == 'dic': filename = get_normalized_path(value, encode=0) path = os.path.join(self.aya_dir, filename) self.dic_files.append(path) elif key == 'log': path = os.path.join(self.aya_dir, str(value)) try: f = open(path, 'w') except: logging.debug('cannnot open {0}'.format(path)) else: if self.logfile: self.logfile.close() self.logfile = f self.global_namespace.put('log', str(value)) elif key == 'logmode': pass # FIXME elif key == 'aitalkinterval': # Ver.3 if not self.global_namespace.exists('aitalkinterval'): try: int(value) except ValueError: logging.debug( 'Could not convert {0} to an integer'.format(value)) else: self.global_namespace.put('aitalkinterval', int(value)) elif key is not None and key != '': try: value = int(value) except: value = str(value) self.global_namespace.put(key, value) def get_dictionary(self): return self.dic def get_ghost_dir(self): return self.aya_dir def get_global_namespace(self): return self.global_namespace def get_system_functions(self): return self.system_functions def get_boot_time(self): return self.boot_time def unload(self): self.request('NOTIFY SHIORI/3.0\r\n' \ 'ID: OnUnload\r\n' \ 'Sender: AYA\r\n' \ 'SecurityLevel: local\r\n\r\n') self.global_namespace.save_database() self.saori_library.unload() if self.logfile is not None: self.logfile.close() for key in self.filelist.keys(): self.filelist[key].close() # SHIORI API def request(self, req_string): header = req_string.splitlines() line = header.pop(0) if line: line = line.strip() req_list = line.split() if len(req_list) >= 2: self.req_command = line_strip(req_list[0]) self.req_protocol = line_strip(req_list[1]) for line in header: line = line_strip(line) if not line: continue if ':' not in line: continue key, value = [line_strip(x) for x in line.split(':', 1)] try: value = int(value) except: value = str(value) self.req_key.append(key) self.req_header[key] = value for key in self.req_header: if isinstance(self.req_header[key], str): self.req_header[key] = unicode( self.req_header[key], 'Shift_JIS', 'ignore').encode('EUC-JP', 'ignore') ## FIXME if self.first_boot: if self.req_header['ID'] in ['OnBoot', 'OnVanished', 'OnGhostChanged']: self.first_boot = 0 logging.debug( 'We lost the {0}. Initializing....'.format(self.__DBNAME)) self.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') elif self.req_header['ID'] == 'OnFirstBoot': self.first_boot = 0 result = '' func = self.dic.get_function('OnRequest') if not func and 'ID' in self.req_header: # Ver.3 for i in range(9): self.global_namespace.remove(''.join(('reference', str(i)))) for i in range(9): key = ''.join(('Reference', str(i))) if key in self.req_header: self.global_namespace.put(''.join(('reference', str(i))), self.req_header[key]) if not self.req_header['ID'].startswith('On'): prefix = 'On_' else: prefix = '' func = self.dic.get_function( ''.join((prefix, self.req_header['ID']))) if func: result = func.call() if self.ver_3 and 'ID' in self.req_header and \ self.req_header['ID'] == 'OnSecondChange': # Ver.3 aitalkinterval = self.global_namespace.get('aitalkinterval') if aitalkinterval > 0: self.aitalk += 1 if self.aitalk > aitalkinterval: self.aitalk = 0 result = self.request('GET SHIORI/3.0\r\n' \ 'ID: OnAiTalk\r\n' \ 'Sender: ninix\r\n' \ 'SecurityLevel: local\r\n\r\n') self.reset_request() return result self.reset_request() if self.ver_3: # Ver.3 result = 'SHIORI/3.0 200 OK\r\n' \ 'Sender: AYA\r\n' \ 'Value: {0}\r\n\r\n'.format( \ unicode(result, 'EUC-JP', 'ignore').encode('Shift_JIS', 'ignore')) ## FIXME return result else: return unicode(result, 'EUC-JP','ignore').encode('Shift_JIS', 'ignore') ## FIXME class AyaSecurity(object): __DENY = 0 __ACCEPT = 1 __CONFIG = 'aya_security.cfg' def __init__(self, aya): self.__aya = aya self.__cfg = '' self.__aya_dir = os.path.abspath(self.__aya.aya_dir) self.__fwrite = [[self.__DENY, '*'], [self.__ACCEPT, self.__aya_dir]] self.__fread = [[self.__ACCEPT, '*']] self.__loadlib = [[self.__ACCEPT, '*']] self.__logfile = None self.load_cfg() self.__fwrite.reverse() self.__fread.reverse() def load_cfg(self): path = [] head, tail = os.path.split(self.__aya_dir) current = head while tail: path.append(tail) head, tail = os.path.split(current) current = head else: path.append(current) current_dir = '' while path: current_dir = os.path.join(current_dir, path.pop()) if os.path.exists(os.path.join(current_dir, self.__CONFIG)): self.__cfg = os.path.join(current_dir, self.__CONFIG) break else: # default setting logging.warning('*WARNING : aya_security.cfg - file not found.') return try: with open(self.__cfg) as f: name = '' data = {} self.comment = 0 line = self.readline(f) while line: if '[' in line: if name: name = self.expand_path(name) if name == '*': break if self.__aya_dir.startswith(name): break data = {} start = line.find('[') end = line.find(']') if end < 0: end = len(line) name = line[start + 1:end] else: if ',' in line: key, value = [x.strip() for x in line.split(',', 1)] if key.startswith('deny.'): key = key[5:] list_ = data.get(key, []) list_.append([self.__DENY, value]) data[key] = list_ elif key.startswith('accept.'): key = key[7:] list_ = data.get(key, []) list_.append([self.__ACCEPT, value]) data[key] = list_ elif key == 'log': head, tail = os.path.split(self.__cfg) value = ''.join(('./', value)) value = os.path.join(head, value) value = os.path.abspath(value) data[key] = value else: pass # error line = self.readline(f) else: if not name: logging.warning('*WARNING : aya_security.cfg - no entry found for {0}.'.format( \ os.path.join(self.__aya_dir, 'aya.dll'))) return except IOError: logging.debug('cannot read aya.txt') return except AyaError as error: logging.debug(error) return self.__fwrite.extend(data.get('fwrite', [])) for i in range(len(self.__fwrite)): self.__fwrite[i][1] = self.expand_path(self.__fwrite[i][1]) self.__fread.extend(data.get('fread', [])) for i in range(len(self.__fread)): self.__fread[i][1] = self.expand_path(self.__fread[i][1]) self.__loadlib.extend(data.get('loadlib', [])) if 'log' in data: self.__logfile = data['log'] def expand_path(self, path): head, tail = os.path.split(self.__cfg) if path == '*': return path if path == '': return head meta = path.rfind('%CFGDIR') if meta >= 0: path = get_normalized_path(path[meta + 7:], encode=0) path = ''.join(('./', path)) path = os.path.join(head, path) else: path = get_normalized_path(path, encode=0) path = os.path.abspath(path) return path def readline(self, f): for line in f: line = unicode(line, 'Shift_JIS', 'ignore').encode('EUC-JP', 'ignore') ## FIXME if self.comment: end = find_not_quoted(line, '*/') if end < 0: continue else: line = line[end + 2:] self.comment = 0 while 1: start, end = find_comment(line) if start < 0: break if end < 0: self.comment = 1 line = line[:start] break line = ''.join((line[:start], ' ', line[end:])) line = line_strip(line) if not line: continue break return line def check_path(self, path, flag='w'): result = 0 abspath = os.path.abspath(path) head, tail = os.path.split(abspath) if tail != 'aya_security.cfg': if flag in ['w', 'w+', 'r+', 'a', 'a+']: for perm, name in self.__fwrite: if name == '*' or abspath[:len(name)] == name: if perm == self.__ACCEPT: result = 1 elif perm == self.__DENY: result = 0 else: continue break elif flag == 'r': result = 1 # default for perm, name in self.__fread: if name == '*' or abspath[:len(name)] == name: if perm == self.__ACCEPT: result = 1 elif perm == self.__DENY: result = 0 else: continue break if self.__logfile and result == 0: if flag == 'r': self.logging(u'許可されていないファイルまたはディレクトリ階層の読み取りをブロックしました.'.encode('EUC-JP'), 'file', abspath) else: self.logging(u'許可されていないファイルまたはディレクトリ階層への書き込みをブロックしました.'.encode('EUC-JP'), 'file', abspath) return result def check_lib(self, dll): result = 1 # default head, tail = os.path.split(get_normalized_path(dll, encode=0)) dll_name = tail for perm, name in self.__loadlib: if name == '*' or dll_name == name: if perm == self.__ACCEPT: result = 1 elif perm == self.__DENY: result = 0 if self.__logfile and result == 0: self.logging(u'許可されていない DLL のロードをブロックしました.'.encode('EUC-JP'), 'dll ', dll_name) return result def logging(self, message, target_type, target): ## FIXME if self.__logfile is None: return None else: try: with open(self.__logfile, 'a') as f: ninix.lock.lockfile(f) aya_dll = os.path.join(self.__aya_dir, 'aya.dll') line = ''.join(('*WARNING : ', str(message), '\n')) line = ''.join((line, 'AYA : ', aya_dll, '\n')) line = ''.join((line, 'date : ', time.strftime('%Y/%m/%d(%a) %H:%M:%S'), '\n')) line = ''.join((line, str(target_type), ' : ', str(target), '\n')) f.write(line) f.write('\n') ninix.lock.unlockfile(f) except: logging.debug('cannnot open {0}'.format(self.__logfile)) return None return None class AyaDictionary(object): def __init__(self, aya): self.aya = aya self.functions = {} self.global_macro = {} def get_function(self, name): return self.functions.get(name, None) def load(self, f, encrypted): all_lines = [] local_macro = {} logical_line = '' comment = 0 while 1: if encrypted: line = decrypt_readline(f) else: line = f.readline() line = unicode(line, 'Shift_JIS', 'ignore').encode('EUC-JP', 'ignore') ## FIXME if not line: break # EOF if comment: end = find_not_quoted(line, '*/') if end < 0: continue else: line = line[end + 2:] comment = 0 while 1: start, end = find_comment(line) if start < 0: break if end < 0: comment = 1 line = line[:start] break line = ''.join((line[:start], ' ', line[end:])) line = line_strip(line) if not line: continue if line.endswith('/'): logical_line = ''.join((logical_line, line[:-1])) else: logical_line = ''.join((logical_line, line)) buf = line # preprosess if buf.startswith('#'): buf = line_strip(buf[1:]) for (tag, target) in [('define', local_macro), ('globaldefine', self.global_macro)]: if buf.startswith(tag): buf = line_strip(buf[len(tag):]) i = 0 while i < len(buf): if buf[i] == ' ' or buf[i] == '\t' or \ buf[i:i + 2] == u' '.encode('EUC-JP'): key = line_strip(buf[:i]) target[key] = line_strip(buf[i:]) break i += 1 break logical_line = '' # reset continue for macro in [local_macro, self.global_macro]: logical_line = self.preprosess(macro, logical_line) # multi statement list_lines = self.split_line(line_strip(logical_line)) if list_lines: all_lines.extend(list_lines) logical_line = '' # reset for line in all_lines: while 1: pos = find_not_quoted(line, u' '.encode('EUC-JP')) if pos >= 0: line = ''.join((line[:pos], ' ', line[pos + 2:])) else: break self.evaluate_lines(all_lines, os.path.split(f.name)[1]) def split_line(self, line): lines = [] while 1: if not line: break pos = len(line) # not len(line) - 1 token = '' for x in ['{', '}']: pos_new = find_not_quoted(line, x) if 0 <= pos_new < pos: pos = pos_new token = x new = line_strip(line[:pos]) line = line_strip(line[pos + len(token):]) if new: lines.append(new) if token != '': lines.append(token) return lines def preprosess(self, macro, line): for key, value in macro.items(): line = line.replace(key, value) return line __SPECIAL_CHARS = [r']', r'(', r')', r'[', r'+', r'-', r'*', r'/', r'=', r':', r';', r'!', r'{', r'}', r'%', r'&', r'#', r'"', r'<', r'>', r',', r'?'] def evaluate_lines(self, lines, file_name): prev = None name = None function = [] option = None block_nest = 0 for i in range(len(lines)): line = lines[i] if line == '{': if name is not None: if block_nest > 0: function.append(line) block_nest += 1 else: if prev is None: logging.debug( 'syntax error in {0}: unbalanced "{" at ' 'the top of file'.format(file_name)) else: logging.debug( 'syntax error in {0}: unbalanced "{" at ' 'the bottom of function "{1}"'.format(file_name, prev)) elif line == '}': if name is not None: block_nest -= 1 if block_nest > 0: function.append(line) elif block_nest == 0: self.functions[name] = AyaFunction(self, name, function, option) # reset prev = name name = None function = [] option = None else: if prev is None: logging.debug( 'syntax error in {0}: unbalanced "}" at ' 'the top of file'.format(file_name)) else: logging.debug( 'syntax error in {0}: unbalanced "}" at ' 'the bottom of function "{1}"'.format(file_name, prev)) block_nest = 0 elif name is None: if ':' in line: name, option = [x.strip() for x in line.split(':', 1)] else: name = line for char in self.__SPECIAL_CHARS: if char in name: logging.debug( 'illegal function name "{0}" in {1}'.format( name, file_name)) function = [] else: if name is not None and block_nest > 0: function.append(line) else: logging.debug('syntax error in {0}: {1}'.format(file_name, line)) class AyaFunction(object): __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 = re.compile('[-+]?\d+(\.\d*)$') __re_d = re.compile('[-+]?\d+$') __re_b = re.compile('[-+]?0[bB][01]+$') __re_x = re.compile('[-+]?0[xX][\dA-Fa-f]+$') __re_if = re.compile('if\s') __re_elseif = re.compile('elseif\s') __re_while = re.compile('while\s') __re_for = re.compile('for\s') __re_switch = re.compile('switch\s') __re_case = re.compile('case\s') __re_when = re.compile('when\s') __SPECIAL_CHARS = [r']', r'(', r')', r'[', r'+', r'-', r'*', r'/', r'=', r':', r';', r'!', r'{', r'}', r'%', r'&', r'#', r'"', r'<', r'>', r',', r'?'] def __init__(self, dic, name, lines, option): self.dic = dic self.name = name self.status = self.__CODE_NONE self.lines = self.parse(lines) if option == 'nonoverlap': self.nonoverlap = [[], [], []] else: self.nonoverlap = None if option == 'sequential': self.sequential = [[], []] else: self.sequential = None def parse(self, lines): result = [] i = 0 while i < len(lines): line = lines[i] if line == '--': result.append([self.__TYPE_DECISION, []]) elif line == 'return': result.append([self.__TYPE_RETURN, []]) elif line == 'break': result.append([self.__TYPE_BREAK, []]) elif line == 'continue': result.append([self.__TYPE_CONTINUE, []]) elif line == '{': inner_func = [] i, inner_func = self.get_block(lines, i) result.append([self.__TYPE_BLOCK, self.parse(inner_func)]) elif self.__re_if.match(line): inner_blocks = [] while 1: current_line = lines[i] if self.__re_if.match(current_line): condition_tokens = AyaStatement( current_line[2:].strip()).tokens condition = self.parse_condition(condition_tokens) elif self.__re_elseif.match(current_line): condition_tokens = AyaStatement( current_line[6:].strip()).tokens condition = self.parse_condition(condition_tokens) else: condition = [self.__TYPE_CONDITION, None] inner_block = [] i, inner_block = self.get_block(lines, i + 1) if condition is None: inner_blocks = [] break entry = [] entry.append(condition) entry.append(self.parse(inner_block)) inner_blocks.append(entry) if i + 1 >= len(lines): break next_line = lines[i + 1] if not self.__re_elseif.match(next_line) and \ next_line != 'else': break i = i + 1 if inner_blocks: result.append([self.__TYPE_IF, inner_blocks]) elif self.__re_while.match(line): condition_tokens = AyaStatement(line[5:].strip()).tokens condition = self.parse_condition(condition_tokens) inner_block = [] i, inner_block = self.get_block(lines, i + 1) result.append([self.__TYPE_WHILE, [condition, self.parse(inner_block)]]) elif self.__re_for.match(line): inner_block = [] i, inner_block = self.get_block(lines, i + 1) end = find_not_quoted(line, ';') if end < 0: logging.debug( 'syntax error in function "{0}": ' 'illegal for statement "{1}"'.format(self.name, line)) else: init = self.parse([line[3:end].strip()]) condition = line[end + 1:].strip() end = find_not_quoted(condition, ';') if end < 0: logging.debug( 'syntax error in function "{0}": ' 'illegal for statement "{1}"'.format(self.name, line)) else: reset = self.parse([condition[end + 1:].strip()]) condition_tokens = AyaStatement( condition[:end].strip()).tokens condition = self.parse_condition(condition_tokens) if condition is not None: result.append([self.__TYPE_FOR, [[init, condition, reset], self.parse(inner_block)]]) elif self.__re_switch.match(line): index = self.parse_token(line[6:].strip()) ##assert index[0] in [] # FIXME inner_block = [] i, inner_block = self.get_block(lines, i + 1) result.append([self.__TYPE_SWITCH, [index, self.parse(inner_block)]]) elif self.__re_case.match(line): left = self.parse_token(line[4:].strip()) ## assert left[0] in [] # FIXME i, block = self.get_block(lines, i + 1) inner_blocks = [] j = 0 while 1: current_line = block[j] if self.__re_when.match(current_line): right = current_line[4:].strip() else: # 'others' right = None inner_block = [] j, inner_block = self.get_block(block, j + 1) if right is not None: argument = AyaArgument(right) while argument.has_more_tokens(): entry = [] right = argument.next_token() tokens = AyaStatement(right).tokens if tokens[0] in ['-', '+']: value_min = self.parse_statement([tokens.pop(0), tokens.pop(0)]) else: value_min = self.parse_statement([tokens.pop(0)]) value_max = value_min if tokens: if tokens[0] != '-': logging.debug( 'syntax error in function ' '"{0}": when {1}'.format(self.name, right)) continue else: tokens.pop(0) if len(tokens) > 2 or \ (len(tokens) == 2 and \ tokens[0] not in ['-', '+']): logging.debug( 'syntax error in function ' '"{0}": when {1}'.format(self.name, right)) continue else: value_max = self.parse_statement(tokens) entry.append([value_min, value_max]) entry.append(self.parse(inner_block)) inner_blocks.append(entry) else: entry = [] entry.append(right) entry.append(self.parse(inner_block)) inner_blocks.append(entry) if j + 1 == len(block): break next_line = block[j + 1] if not self.__re_when.match(next_line) and \ next_line != 'others': break j += 1 result.append([self.__TYPE_CASE, [left, inner_blocks]]) elif find_not_quoted(line, ';') >= 0: end = find_not_quoted(line, ';') new_line = line[end + 1:].strip() line = line[:end].strip() new_lines = lines[:i] if line: new_lines.append(line) if new_line: new_lines.append(new_line) new_lines.extend(lines[i + 1:]) lines = new_lines continue elif self.is_substitution(line): tokens = AyaStatement(line).tokens left = self.parse_token(tokens[0]) if left[0] not in [self.__TYPE_ARRAY, self.__TYPE_VARIABLE, self.__TYPE_TOKEN]: logging.debug( 'syntax error in function "{0}": ' 'illegall substitution "{1}"'.format(self.name, line)) else: if left[0] == self.__TYPE_TOKEN: # this cannot be FUNCTION left[0] = self.__TYPE_VARIABLE left[1] = [left[1], None] ope = [self.__TYPE_OPERATOR, tokens[1]] right = self.parse_statement(tokens[2:]) result.append([self.__TYPE_SUBSTITUTION, [left, ope, right]]) elif self.is_inc_or_dec(line): # ++/-- ope = line[-2:] var = self.parse_token(line[:-2]) if var[0] not in [self.__TYPE_ARRAY, self.__TYPE_VARIABLE, self.__TYPE_TOKEN]: logging.debug( 'syntax error in function "{0}": ' 'illegall increment/decrement "{1}"'.format(self.name, line)) else: if var[0] == self.__TYPE_TOKEN: var[0] = self.__TYPE_VARIABLE var[1] = [var[1], None] if ope == '++': result.append([self.__TYPE_INC, var]) elif ope == '--': result.append([self.__TYPE_DEC, var]) else: return None # should not reach here else: tokens = AyaStatement(line).tokens if tokens[-1] == '"': # This is kluge. logging.debug( 'syntax error in function "{0}": ' 'unbalanced \'"\' or \'"\' in string {1}'.format(self.name, ''.join(tokens))) token = ''.join(tokens[:-1]) if token and token[0] == '"': token = token[1:] if token: if '%' not in token: result.append([self.__TYPE_STRING_LITERAL, token]) else: result.append([self.__TYPE_STRING, token]) elif len(tokens) == 1: result.append(self.parse_token(tokens[0])) else: result.append(self.parse_statement(tokens)) i += 1 result.append([self.__TYPE_DECISION, []]) return result def parse_statement(self, statement_tokens): n_tokens = len(statement_tokens) statement = [] if n_tokens == 1: statement = [self.__TYPE_STATEMENT, self.parse_token(statement_tokens[0])] elif statement_tokens[0] in ['+', '-']: tokens = ['0'] tokens.extend(statement_tokens) statement = self.parse_statement(tokens) else: ope_index = None for ope in ['+', '-']: if ope in statement_tokens: new_index = statement_tokens.index(ope) if ope_index is None or new_index < ope_index: ope_index = new_index if ope_index is None: statement_tokens.reverse() try: for ope in ['*', '/', '%']: if ope in statement_tokens: new_index = statement_tokens.index(ope) if ope_index is None or new_index < ope_index: ope_index = new_index if ope_index is not None: ope_index = -1 - ope_index finally: statement_tokens.reverse() if ope_index in [None, -1, 0, n_tokens - 1]: if statement_tokens[0].startswith('"') and \ statement_tokens[0].endswith('"') and \ statement_tokens[-1].startswith('"') and \ statement_tokens[-1].endswith('"'): logging.debug( 'syntax error in function "{0}": ' '\'"\' in string {1}'.format(self.name, ' '.join(statement_tokens))) return self.parse_token(' '.join(statement_tokens)) else: logging.debug( 'syntax error in function "{0}": ' 'illegal statement "{1}"'.format(self.name, ' '.join(statement_tokens))) return [] else: ope = [self.__TYPE_OPERATOR, statement_tokens[ope_index]] if len(statement_tokens[:ope_index]) == 1: if statement_tokens[0].startswith('('): tokens = AyaStatement(statement_tokens[0][1:-1]).tokens left = self.parse_statement(tokens) else: left = self.parse_token( statement_tokens[:ope_index][0]) else: left = self.parse_statement(statement_tokens[:ope_index]) if len(statement_tokens[ope_index + 1:]) == 1: if statement_tokens[-1].startswith('('): tokens = AyaStatement( statement_tokens[ope_index + 1][1:-1]).tokens right = self.parse_statement(tokens) else: right = self.parse_token( statement_tokens[ope_index + 1:][0]) else: right = self.parse_statement( statement_tokens[ope_index + 1:]) statement = [self.__TYPE_STATEMENT, left, ope, right] return statement def parse_condition(self, condition_tokens): n_tokens = len(condition_tokens) condition = None ope_index = None condition_tokens.reverse() try: for ope in ['&&', '||']: if ope in condition_tokens: new_index = condition_tokens.index(ope) if ope_index is None or new_index < ope_index: ope_index = new_index if ope_index is not None: ope_index = -1 - ope_index finally: condition_tokens.reverse() if ope_index is None: for ope in ['==', '!=', '>', '<', '>=', '<=', '_in_', '!_in_']: if ope in condition_tokens: new_index = condition_tokens.index(ope) if ope_index is None or new_index < ope_index: ope_index = new_index if ope_index in [None, -1, 0, n_tokens - 1]: logging.debug( 'syntax error in function "{0}": ' 'illegal condition "{1}"'.format(self.name, ' '.join(condition_tokens))) return None ope = [self.__TYPE_OPERATOR, condition_tokens[ope_index]] if len(condition_tokens[:ope_index]) == 1: left = self.parse_token(condition_tokens[:ope_index][0]) else: left = self.parse_statement(condition_tokens[:ope_index]) if len(condition_tokens[ope_index + 1:]) == 1: right = self.parse_token(condition_tokens[ope_index + 1:][0]) else: right = self.parse_statement(condition_tokens[ope_index + 1:]) condition = [self.__TYPE_CONDITION, [left, ope, right]] else: ope = [self.__TYPE_OPERATOR, condition_tokens[ope_index]] left = self.parse_condition(condition_tokens[:ope_index]) right = self.parse_condition(condition_tokens[ope_index + 1:]) if left is not None and right is not None: condition = [self.__TYPE_CONDITION, [left, ope, right]] return condition def parse_argument(self, args): argument = AyaArgument(args) arguments = [] while argument.has_more_tokens(): token = argument.next_token() if token.startswith('&'): result = self.parse_token(token[1:]) if result[0] == self.__TYPE_ARRAY: arguments.append([self.__TYPE_ARRAY_POINTER, result[1]]) elif result[0] == self.__TYPE_VARIABLE: arguments.append([self.__TYPE_VARIABLE_POINTER, result[1]]) elif result[0] == self.__TYPE_TOKEN: arguments.append([self.__TYPE_VARIABLE_POINTER, [result[1], None]]) else: logging.debug( 'syntax error in function "{0}": ' 'illegal argument "{1}"'.format(self.name, token)) elif token.startswith('('): if not token.endswith(')'): logging.debug( 'syntax error in function "{0}": ' 'unbalanced "(" in the string({1})'.format(self.name, token)) return None else: statement = AyaStatement(token[1:-1]) arguments.append(self.parse_statement(statement.tokens)) else: arguments.append(self.parse_statement([token])) return arguments def parse_token(self, token): result = [] if self.__re_f.match(token): result = [self.__TYPE_FLOAT, token] elif self.__re_d.match(token): result = [self.__TYPE_INT, token] elif token.startswith('"'): text = token[1:] if text.endswith('"'): text = text[:-1] if text.count('"') > 0: logging.debug( 'syntax error in function "{0}": ' '\'"\' in string "{1}"'.format(self.name, text)) if '%' not in text: result = [self.__TYPE_STRING_LITERAL, text] else: result = [self.__TYPE_STRING, text] else: pos_parenthesis_open = token.find('(') pos_block_open = token.find('[') if pos_parenthesis_open != -1 and \ (pos_block_open == -1 or \ pos_parenthesis_open < pos_block_open): # function if not token.endswith(')'): logging.debug( 'syntax error: unbalnced "(" in "{0}"'.format(token)) else: func_name = token[:pos_parenthesis_open] arguments = self.parse_argument( token[pos_parenthesis_open + 1:-1]) for char in self.__SPECIAL_CHARS: if char in func_name: logging.debug( 'illegal character "{0}" in ' 'the name of function "{1}"'.format(char, token)) break else: if self.dic.aya.get_system_functions().exists( func_name): if func_name == 'LOGGING': result = [self.__TYPE_SYSTEM_FUNCTION, [func_name, arguments, token[pos_parenthesis_open + 1:-1]]] else: result = [self.__TYPE_SYSTEM_FUNCTION, [func_name, arguments]] else: result = [self.__TYPE_FUNCTION, [func_name, arguments]] elif pos_block_open != -1: # array if not token.endswith(']'): logging.debug( 'syntax error: unbalnced "[" in "{0}"'.format(token)) else: array_name = token[:pos_block_open] index = self.parse_token(token[pos_block_open + 1:-1]) for char in self.__SPECIAL_CHARS: if char in array_name: logging.debug( 'illegal character "{0}" in ' 'the name of array "{1}"'.format(char, token)) break else: result = [self.__TYPE_ARRAY, [array_name, index]] else: # variable or function for char in self.__SPECIAL_CHARS: if char in token: logging.debug( 'syntax error in function "{0}": ' 'illegal character "{1}" in the name of ' 'function/variable "{2}"'.format(self.name, char, token)) break else: result = [self.__TYPE_TOKEN, token] return result def call(self, argv=None): namespace = AyaNamespace(self.dic.aya) _argv = [] if not argv: namespace.put('_argc', 0) else: namespace.put('_argc', len(argv)) for i in range(len(argv)): if isinstance(argv[i], dict): _argv.append(argv[i]['value']) else: _argv.append(argv[i]) namespace.put('_argv', _argv) self.status = self.__CODE_NONE result = self.evaluate(namespace, self.lines, -1, 0) if result is None: result = '' if argv: for i in range(len(argv)): if isinstance(argv[i], dict): value = _argv[i] name = argv[i]['name'] namespace = argv[i]['namespace'] index = argv[i]['index'] namespace.put(name, value, index) return result def evaluate(self, namespace, lines, index_to_return, is_inner_block): result = [] alternatives = [] for line in lines: if not line: continue if line[0] in [self.__TYPE_DECISION, self.__TYPE_RETURN, self.__TYPE_BREAK, self.__TYPE_CONTINUE] or \ self.status in [self.__CODE_RETURN, self.__CODE_BREAK, self.__CODE_CONTINUE]: if alternatives: if is_inner_block: if index_to_return < 0: result.append(random.choice(alternatives)) elif index_to_return <= len(alternatives) - 1: result.append(alternatives[index_to_return]) else: # out of range result.append('') else: result.append(alternatives) alternatives = [] if line[0] == self.__TYPE_RETURN or \ self.status == self.__CODE_RETURN: self.status = self.__CODE_RETURN break elif line[0] == self.__TYPE_BREAK or \ self.status == self.__CODE_BREAK: self.status = self.__CODE_BREAK break elif line[0] == self.__TYPE_CONTINUE or \ self.status == self.__CODE_CONTINUE: self.status = self.__CODE_CONTINUE break elif line[0] == self.__TYPE_BLOCK: inner_func = line[1] local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_func = self.evaluate(local_namespace, inner_func, -1, 1) if result_of_inner_func: alternatives.append(result_of_inner_func) elif line[0] == self.__TYPE_SUBSTITUTION: left, ope, right = line[1] ##assert left[0] in [self.__TYPE_ARRAY, self.__TYPE_VARIABLE] ##assert ope[0] == self.__TYPE_OPERATOR ope = ope[1] if ope in [':=', '+:=', '-:=', '*:=', '/:=', '%:=']: type_float = 1 else: type_float = 0 ##assert right[0] == self.__TYPE_STATEMENT right_result = self.evaluate_statement(namespace, right, type_float) if ope not in ['=', ':=']: left_result = self.evaluate_token(namespace, left) right_result = self.operation(left_result, ope[0], right_result, type_float) ope = ope[1:] self.substitute(namespace, left, ope, right_result) elif line[0] == self.__TYPE_INC or \ line[0] == self.__TYPE_DEC: # ++/-- if line[0] == self.__TYPE_INC: ope = '++' elif line[0] == self.__TYPE_DEC: ope = '--' else: return None # should not reach here var = line[1] ## assert var[0] in [self.__TYPE_ARRAY, self.__TYPE_VARIABLE] var_name = var[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() value = self.evaluate_token(namespace, var) if var[0] == self.__TYPE_ARRAY: # _argv[n] only index = self.evaluate_token(namespace, var[1][1]) try: index = int(index) except: logging.debug( 'index of array has to be integer: ' '{0}[{1}]'.format(var_name, var[1][1][0])) return None else: index = None if isinstance(value, (int, float)): if ope == '++': target_namespace.put(var_name, int(value) + 1, index) elif ope == '--': target_namespace.put(var_name, int(value) - 1, index) else: return None # should not reach here else: logging.debug( 'illegal increment/decrement:' 'type of variable {0} is not number'.format(var_name)) elif line[0] == self.__TYPE_IF: inner_blocks = line[1] n_blocks = len(inner_blocks) for j in range(n_blocks): entry = inner_blocks[j] condition = entry[0] inner_block = entry[1] assert condition[0] == self.__TYPE_CONDITION if condition is None or \ self.evaluate_condition(namespace, condition): local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_block = self.evaluate(local_namespace, inner_block, -1, 1) if result_of_inner_block: alternatives.append(result_of_inner_block) break elif line[0] == self.__TYPE_WHILE: condition = line[1][0] inner_block = line[1][1] assert condition[0] == self.__TYPE_CONDITION while self.evaluate_condition(namespace, condition): local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_block = self.evaluate(local_namespace, inner_block, -1, 1) if result_of_inner_block is not None: alternatives.append(result_of_inner_block) if self.status == self.__CODE_RETURN: break if self.status == self.__CODE_BREAK: self.status = self.__CODE_NONE break if self.status == self.__CODE_CONTINUE: self.status = self.__CODE_NONE elif line[0] == self.__TYPE_FOR: init = line[1][0][0] condition = line[1][0][1] reset = line[1][0][2] inner_block = line[1][1] self.evaluate(namespace, init, -1, 1) assert condition[0] == self.__TYPE_CONDITION while self.evaluate_condition(namespace, condition): local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_block = self.evaluate(local_namespace, inner_block, -1, 1) if result_of_inner_block is not None: alternatives.append(result_of_inner_block) if self.status == self.__CODE_RETURN: break if self.status == self.__CODE_BREAK: self.status = self.__CODE_NONE break if self.status == self.__CODE_CONTINUE: self.status = self.__CODE_NONE self.evaluate(namespace, reset, -1, 1) elif line[0] == self.__TYPE_SWITCH: index = self.evaluate_token(namespace, line[1][0]) inner_block = line[1][1] try: index = int(index) except ValueError: index = 0 local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_block = self.evaluate(local_namespace, inner_block, index, 1) if result_of_inner_block: alternatives.append(result_of_inner_block) elif line[0] == self.__TYPE_CASE: left = self.evaluate_token(namespace, line[1][0]) inner_blocks = line[1][1] n_blocks = len(inner_blocks) default_result = None for j in range(n_blocks): entry = inner_blocks[j] inner_block = entry[1] local_namespace = AyaNamespace(self.dic.aya, namespace) if entry[0] is not None: value_min, value_max = entry[0] value_min = self.evaluate_statement(namespace, value_min, 1) value_max = self.evaluate_statement(namespace, value_max, 1) if value_min <= left <= value_max: result_of_inner_block = self.evaluate( local_namespace, inner_block, -1, 1) if result_of_inner_block: alternatives.append(result_of_inner_block) break else: default_result = self.evaluate(local_namespace, inner_block, -1, 1) else: if default_result: alternatives.append(default_result) elif line[0] == self.__TYPE_STATEMENT: result_of_func = self.evaluate_statement(namespace, line, 0) if result_of_func: alternatives.append(result_of_func) else: result_of_eval = self.evaluate_token(namespace, line) if result_of_eval: alternatives.append(result_of_eval) if not is_inner_block: if self.sequential is not None: list_ = [] for alt in result: list_.append(len(alt)) if self.sequential[0] != list_: self.sequential[0] = list_ self.sequential[1] = [0] * len(result) else: for index in range(len(result)): current = self.sequential[1][index] if current < len(result[index]) - 1: self.sequential[1][index] = current + 1 break else: self.sequential[1][index] = 0 if self.nonoverlap is not None: list_ = [] for alt in result: list_.append(len(alt)) if self.nonoverlap[0] != list_: self.nonoverlap[0] = list_ self.nonoverlap[2] = [] if not self.nonoverlap[2]: self.nonoverlap[2].append([0] * len(result)) while 1: new = [] new.extend(self.nonoverlap[2][-1]) for index in range(len(result)): if new[index] < len(result[index]) - 1: new[index] += 1 self.nonoverlap[2].append(new) break else: new[index] = 0 else: break next = random.choice(range(len(self.nonoverlap[2]))) self.nonoverlap[1] = self.nonoverlap[2][next] del self.nonoverlap[2][next] for index in range(len(result)): if self.sequential is not None: result[index] = result[index][self.sequential[1][index]] elif self.nonoverlap is not None: result[index] = result[index][self.nonoverlap[1][index]] else: result[index] = random.choice(result[index]) if not result: return None elif len(result) == 1: return result[0] else: return ''.join([str(s) for s in result]) def substitute(self, namespace, left, ope, right): var_name = left[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() if left[0] is not self.__TYPE_ARRAY: target_namespace.put(var_name, right) else: index = self.evaluate_token(namespace, left[1][1]) try: index = int(index) except ValueError: logging.debug('Could not convert {0} to an integer'.format(index)) else: if ope == '=': elem = right elif ope == ':=': if isinstance(right, int): elem = float(right) else: elem = right else: return None # should not reach here target_namespace.put(var_name, elem, index) def evaluate_token(self, namespace, token): result = '' # default if token[0] == self.__TYPE_TOKEN: if self.__re_b.match(token[1]): pos = self.__re_d.search(token[1]).start() result = int(token[1][pos:], 2) elif self.__re_x.match(token[1]): result = int(token[1], 16) else: func = self.dic.get_function(token[1]) system_functions = self.dic.aya.get_system_functions() if func: result = func.call() elif system_functions.exists(token[1]): result = system_functions.call(namespace, token[1], []) elif token[1].startswith('random'): # ver.3 result = int(random.randrange(0, 100, 1)) else: if token[1].startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() if target_namespace.exists(token[1]): result = target_namespace.get(token[1]) elif token[0] == self.__TYPE_STRING_LITERAL: result = token[1] elif token[0] == self.__TYPE_STRING: result = self.evaluate_string(namespace, token[1]) elif token[0] == self.__TYPE_INT: result = int(token[1]) elif token[0] == self.__TYPE_FLOAT: result = float(token[1]) elif token[0] == self.__TYPE_SYSTEM_FUNCTION: system_functions = self.dic.aya.get_system_functions() func_name = token[1][0] ##assert system_functions.exists(func_name) ##raise Exception(''.join(('function ', func_name, ' not found.'))) arguments = self.evaluate_argument(namespace, func_name, token[1][1], 1) if func_name == 'CALLBYNAME': func = self.dic.get_function(arguments[0]) system_functions = self.dic.aya.get_system_functions() if func: result = func.call() elif system_functions.exists(arguments[0]): result = system_functions.call(namespace, arguments[0], []) elif func_name == 'LOGGING': arguments.insert(0, token[1][2]) arguments.insert(0, self.name) arguments.insert(0, self.dic.aya.logfile) result = system_functions.call(namespace, func_name, arguments) else: result = system_functions.call(namespace, func_name, arguments) elif token[0] == self.__TYPE_FUNCTION: func_name = token[1][0] func = self.dic.get_function(func_name) ##assert func is not None: ##raise Exception(''.join(('function ', func_name, ' not found.'))) arguments = self.evaluate_argument(namespace, func_name, token[1][1], 0) result = func.call(arguments) elif token[0] == self.__TYPE_ARRAY: var_name = token[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() index = self.evaluate_token(namespace, token[1][1]) try: index = int(index) except: logging.debug( 'index of array has to be integer: {0}[{1}]'.format(var_name, token[1][1])) else: if var_name == 'random': # Ver.3 result = int(random.randrange(0, index, 1)) elif var_name == 'ascii': # Ver.3 if 0 <= index < 0x80: ## FIXME result = chr(index) else: result = ' ' elif target_namespace.exists(var_name): result = target_namespace.get(var_name, index) elif token[0] == self.__TYPE_VARIABLE: var_name = token[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() if target_namespace.exists(var_name): result = target_namespace.get(var_name) elif token[0] == self.__TYPE_ARRAY_POINTER: var_name = token[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() index = self.evaluate_token(namespace, token[1][1]) try: index = int(index) except: logging.debug( 'index of array has to be integer: {0}[{1}]'.format(var_name, token[1][1])) else: if var_name == 'random': # Ver.3 result = int(random.randrange(0, index, 1)) elif var_name == 'ascii': # Ver.3 if 0 <= index < 0x80: ## FIXME result = chr(index) else: result = ' ' else: value = target_namespace.get(var_name, index) result = {'name': var_name, 'index': index, 'namespace': target_namespace, 'value': value} elif token[0] == self.__TYPE_VARIABLE_POINTER: var_name = token[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() value = target_namespace.get(var_name) result = {'name': var_name, 'index': None, 'namespace': target_namespace, 'value': value} else: logging.debug('error in evaluate_token: {0}'.format(token)) return result def evaluate_condition(self, namespace, condition): result = 0 if condition[1] is None: return 1 left = condition[1][0] ope = condition[1][1] right = condition[1][2] assert ope[0] == self.__TYPE_OPERATOR if left[0] == self.__TYPE_CONDITION: left_result = self.evaluate_condition(namespace, left) elif left[0] == self.__TYPE_STATEMENT: left_result = self.evaluate_statement(namespace, left, 1) else: left_result = self.evaluate_token(namespace, left) if right[0] == self.__TYPE_CONDITION: right_result = self.evaluate_condition(namespace, right) elif right[0] == self.__TYPE_STATEMENT: right_result = self.evaluate_statement(namespace, right, 1) else: right_result = self.evaluate_token(namespace, right) if ope[1] == '==': result = (left_result == right_result) elif ope[1] == '!=': result = (left_result != right_result) elif ope[1] == '_in_': if isinstance(right_result, str) and isinstance(left_result, str): if left_result in right_result: result = 1 else: result = 0 else: result = 0 elif ope[1] == '!_in_': if isinstance(right_result, str) and isinstance(left_result, str): if left_result not in right_result: result = 1 else: result = 0 else: result = 0 elif ope[1] == '<': result = left_result < right_result elif ope[1] == '>': result = left_result > right_result elif ope[1] == '<=': result = left_result <= right_result elif ope[1] == '>=': result = left_result >= right_result elif ope[1] == '||': result = left_result or right_result elif ope[1] == '&&': result = left_result and right_result else: pass return result def evaluate_statement(self, namespace, statement, type_float): num = len(statement[1:]) if num == 0: return '' type_ = statement[0] token = statement[1] if type_ == self.__TYPE_STATEMENT: left = self.evaluate_statement(namespace, token, type_float) else: left = self.evaluate_token(namespace, statement) ##else: ## logging.debug('illegal statement: {0}'.format(' '.join(statement[1]))) ## return '' if num == 3: ##assert statement[2][0] == self.__TYPE_OPERATOR ope = statement[2][1] type_ = statement[3][0] if type_ == self.__TYPE_INT: token = statement[3][1] if type_float: right = float(token) else: right = int(token) elif type_ == self.__TYPE_FLOAT: token = statement[3][1] if type_float: right = float(token) else: right = int(float(token)) elif type_ == self.__TYPE_STATEMENT: right = self.evaluate_statement(namespace, statement[3], type_float) else: right = self.evaluate_token(namespace, statement[3]) result = self.operation(left, ope, right, type_float) else: result = left return result def operation(self, left, ope, right, type_float): try: if type_float: left = float(left) right = float(right) elif ope != '+' or \ (not isinstance(left, str) and not isinstance(right, str)): left = int(left) right = int(right) else: left = str(left) right = str(right) except: left = str(left) right = str(right) try: if ope == '+': return left + right elif ope == '-': return left - right elif ope == '*': return left * right elif ope == '/': if right == 0: return 0 else: return left / right elif ope == '%': return left % right except: logging.debug( 'illegal operation: {0}'.format(' '.join((str(left), str(ope), str(right))))) return '' def get_block(self, parent, startpoint): result = [] n_lines = len(parent) inner_nest_level = 0 for i in range(startpoint, n_lines): inner_content = parent[i] if inner_content == '{': if inner_nest_level > 0: result.append(inner_content) inner_nest_level += 1 elif inner_content == '}': inner_nest_level -= 1 if inner_nest_level > 0: result.append(inner_content) else: result.append(inner_content) if inner_nest_level == 0: return i, result return startpoint, result def evaluate_string(self, namespace, line): history = [] # %[n] buf = '' startpoint = 0 system_functions = self.dic.aya.get_system_functions() while startpoint < len(line): pos = line.find('%', startpoint) if pos < 0: buf = ''.join((buf, line[startpoint:])) startpoint = len(line) continue else: buf = ''.join((buf, line[startpoint:pos])) startpoint = pos endpoint = len(line) for char in self.__SPECIAL_CHARS: pos = line.find(char, startpoint + 2, endpoint) if 0 < pos < endpoint: endpoint = pos if line[startpoint + 1] == '[': # history if line[endpoint] != ']': logging.debug( 'unbalanced "%[" or illegal index in ' 'the string({0})'.format(line)) buf = '' break index_token = self.parse_token(line[startpoint + 2:endpoint]) index = self.evaluate_token(namespace, index_token) try: index = int(index) except: logging.debug( 'illegal history index in the string({0})'.format(line)) else: if 0 <= index < len(history): buf = ''.join((buf, self.format(history[index]))) startpoint = endpoint + 1 continue replaced = 0 while endpoint > startpoint + 1: token = line[startpoint + 1:endpoint] if token == 'random' or token == 'ascii': # Ver.3 if endpoint < len(line) and \ line[endpoint] == '[': end_of_block = line.find(']', endpoint + 1) if end_of_block < 0: logging.debug( 'unbalanced "[" or illegal index in ' 'the string({0})'.format(line)) startpoint = len(line) buf = '' break index = self.parse_token(line[endpoint + 1:end_of_block]) content_of_var = self.evaluate_token( namespace, [self.__TYPE_ARRAY, [token, index]]) if content_of_var is None: content_of_var = '' history.append(content_of_var) buf = ''.join((buf, self.format(content_of_var))) startpoint = end_of_block + 1 replaced = 1 break func = self.dic.get_function(token) is_system_func = system_functions.exists(token) if func is not None or is_system_func: if endpoint < len(line) and \ line[endpoint] == '(': end_of_parenthesis = line.find(')', endpoint + 1) if end_of_parenthesis < 0: logging.debug( 'unbalanced "(" in the string({0})'.format(line)) startpoint = len(line) buf = '' break func_name = token arguments = self.parse_argument( line[endpoint + 1:end_of_parenthesis]) arguments = self.evaluate_argument( namespace, func_name, arguments, is_system_func) if is_system_func: if func_name == 'CALLBYNAME': func = self.dic.get_function(arguments[0]) if func: result_of_func = func.call() elif system_functions.exists(arguments[0]): result_of_func = system_functions.call( namespace, arguments[0], []) elif func_name == 'LOGGING': arguments.insert( 0, line[endpoint + 1:end_of_parenthesis]) arguments.insert(0, self.name) arguments.insert(0, self.dic.aya.logfile) result_of_func = system_functions.call( namespace, func_name, arguments) else: result_of_func = system_functions.call( namespace, func_name, arguments) else: result_of_func = func.call(arguments) if result_of_func is None: result_of_func = '' history.append(result_of_func) buf = ''.join((buf, self.format(result_of_func))) startpoint = end_of_parenthesis + 1 replaced = 1 break elif func is not None: result_of_func = func.call() history.append(result_of_func) buf = ''.join((buf, self.format(result_of_func))) startpoint = endpoint replaced = 1 break else: result_of_func = system_functions.call( namespace, token, []) if result_of_func is None: result_of_func = '' history.append(result_of_func) buf = ''.join((buf, self.format(result_of_func))) startpoint = endpoint replaced = 1 break else: if token.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() if target_namespace.exists(token): have_index = 0 index = None if endpoint < len(line) and line[endpoint] == '[': end_of_block = line.find(']', endpoint + 1) if end_of_block < 0: logging.debug( 'unbalanced "[" or ' 'illegal index in the string({0})'.format(line)) startpoint = len(line) buf = '' break have_index = 1 index_token = self.parse_token( line[endpoint + 1:end_of_block]) index = self.evaluate_token(namespace, index_token) try: index = int(index) except ValueError: have_index = 0 index = None value = target_namespace.get(token, index) if value is not None: content_of_var = value history.append(content_of_var) buf = ''.join((buf, self.format(content_of_var))) if have_index: startpoint = end_of_block + 1 else: startpoint = endpoint replaced = 1 break endpoint -= 1 if not replaced: buf = ''.join((buf, line[startpoint])) startpoint += 1 return buf def format(self, input_num): if isinstance(input_num, float): result = str(round(input_num, 6)) else: result = str(input_num) return result def evaluate_argument(self, namespace, name, argument, is_system_func): arguments = [] for i in range(len(argument)): if is_system_func and \ self.dic.aya.get_system_functions().not_to_evaluate(name, i): ## assert argument[i] in [] ## FIXME arguments.append(argument[i][1][1]) else: arguments.append(self.evaluate_statement(namespace, argument[i], 1)) if is_system_func: if name == 'NAMETOVALUE' and \ len(arguments) == 1: # this is kluge arguments[0] = self.evaluate_statement(namespace, argument[0], 1) return arguments def is_substitution(self, line): statement = AyaStatement(line) if statement.countTokens() >= 3: statement.next_token() # left ope = statement.next_token() ope_list = ['=', ':=', '+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:='] if ope in ope_list: return 1 return 0 def is_inc_or_dec(self, line): if len(line) <= 2: return 0 if line.endswith('++') or line.endswith('--'): return 1 else: return 0 class AyaSystemFunctions(object): ## FIXME: EUC-JP -> Unicode (STRLEN, etc.) def __init__(self, aya): self.aya = aya self.saori_statuscode = '' self.saori_header = [] self.saori_value = {} self.saori_protocol = '' self.errno = 0 self.security = AyaSecurity(self.aya) self.functions = { 'TONUMBER': [self.TONUMBER, [0], [1], None], 'TOSTRING': [self.TOSTRING, [0], [1], None], 'TONUMBER2': [self.TONUMBER2, [None], [1], None], 'TOSTRING2': [self.TOSTRING2, [None], [1], None], 'TOUPPER': [self.TOUPPER, [None], [1], None], 'TOLOWER': [self.TOLOWER, [None], [1], None], 'TOBINSTR': [self.TOBINSTR, [None], [1], None], 'TOHEXSTR': [self.TOHEXSTR, [None], [1], None], 'BINSTRTONUM': [self.BINSTRTONUM, [None], [1], None], 'HEXSTRTONUM': [self.HEXSTRTONUM, [None], [1], None], 'ERASEVARIABLE': [self.ERASEVARIABLE, [None], [1], None], 'STRLEN': [self.STRLEN, [1], [1, 2], None], 'STRSTR': [self.STRSTR, [3], [3, 4], None], 'SUBSTR': [self.SUBSTR, [None], [3], None], 'REPLACE': [self.REPLACE, [None], [3], None], 'ERASE': [self.ERASE, [None], [3], None], 'INSERT': [self.INSERT, [None], [3], None], 'CUTSPACE': [self.CUTSPACE, [None], [1], None], 'MSTRLEN': [self.MSTRLEN, [None], [1], None], 'MSTRSTR': [self.MSTRSTR, [None], [3], None], 'MSUBSTR': [self.MSUBSTR, [None], [3], None], 'MERASE': [self.MERASE, [None], [3], None], 'MINSERT': [self.MINSERT, [None], [3], None], 'NAMETOVALUE': [self.NAMETOVALUE, [0], [1, 2], None], 'LETTONAME': [self.LETTONAME, [None], [2], None], 'ARRAYSIZE': [self.ARRAYSIZE, [0, 1], [1, 2], None], 'CALLBYNAME': [self.CALLBYNAME, [None], [1], None], ##'FUNCTIONEX': [self.FUNCTIONEX, [None], [None], None], # FIXME # Ver.3 ##'SAORI': [self.SAORI, [None], [None], None], # FIXME # Ver.3 'RAND': [self.RAND, [None], [0, 1], None], 'ASC': [self.ASC, [None], [1], None], 'IASC': [self.IASC, [None], [1], None], 'FLOOR': [self.FLOOR, [None], [1], None], 'CEIL': [self.CEIL, [None], [1], None], 'ROUND': [self.ROUND, [None], [1], None], 'ISINSIDE': [self.ISINSIDE, [None], [3], None], 'ISINTEGER': [self.ISINTEGER, [None], [1], None], 'ISREAL': [self.ISREAL, [None], [1], None], 'ISFUNCTION': [self.ISFUNCTION, [None], [1], None], 'SIN': [self.SIN, [None], [1], None], 'COS': [self.COS, [None], [1], None], 'TAN': [self.TAN, [None], [1], None], 'LOG': [self.LOG, [None], [1], None], 'LOG10': [self.LOG10, [None], [1], None], 'POW': [self.POW, [None], [2], None], 'SQRT': [self.SQRT, [None], [1], None], 'SETSEPARATOR': [self.SETSEPARATOR, [0], [2], None], 'REQ.COMMAND': [self.REQ_COMMAND, [None], [0], None], 'REQ.HEADER': [self.REQ_HEADER, [None], [1], None], 'REQ.KEY': [self.REQ_HEADER, [None], [1], None], # alias 'REQ.VALUE': [self.REQ_VALUE, [None], [1], None], 'REQ.PROTOCOL': [self.REQ_PROTOCOL, [None], [0], None], 'LOADLIB': [self.LOADLIB, [None], [1], 16], 'UNLOADLIB': [self.UNLOADLIB, [None], [1], None], 'REQUESTLIB': [self.REQUESTLIB, [None], [2], None], 'LIB.STATUSCODE': [self.LIB_STATUSCODE, [None], [0], None], 'LIB.HEADER': [self.LIB_HEADER, [None], [1], None], 'LIB.KEY': [self.LIB_HEADER, [None], [1], None], # alias 'LIB.VALUE': [self.LIB_VALUE, [None], [1], None], 'LIB.PROTOCOL': [self.LIB_PROTOCOL, [None], [0], None], 'FOPEN': [self.FOPEN, [None], [2], 256], 'FCLOSE': [self.FCLOSE, [None], [1], None], 'FREAD': [self.FREAD, [None], [1], None], 'FWRITE': [self.FWRITE, [None], [2], None], 'FWRITE2': [self.FWRITE2, [None], [2], None], 'FCOPY': [self.FCOPY, [None], [2], 259], 'FMOVE': [self.FMOVE, [None], [2], 264], 'FDELETE': [self.FDELETE, [None], [1], 269], 'FRENAME': [self.FRENAME, [None], [2], 273], 'FSIZE': [self.FSIZE, [None], [1], 278], 'MKDIR': [self.MKDIR, [None], [1], 282], 'RMDIR': [self.RMDIR, [None], [1], 286], 'FENUM': [self.FENUM, [None], [1, 2], 290], 'GETLASTERROR': [self.GETLASTERROR, [None], [None], None], 'LOGGING': [self.LOGGING, [None], [4], None] } def exists(self, name): return name in self.functions def call(self, namespace, name, argv): self.errno = 0 if name in self.functions and \ self.check_num_args(name, argv): return self.functions[name][0](namespace, argv) else: return '' def not_to_evaluate(self, name, index): if index in self.functions[name][1]: return 1 else: return 0 def check_num_args(self, name, argv): list_num = self.functions[name][2] if list_num == [None]: return 1 else: if len(argv) in list_num: return 1 list_num.sort() if len(argv) < list_num[0]: errno = self.functions[name][3] if errno is not None: self.errno = errno logging.debug( ''.join((str(name), ': called with too few argument(s)'))) return 0 return 1 def TONUMBER(self, namespace, argv): var = str(argv[0]) target_namespace = self.select_namespace(namespace, var) token = target_namespace.get(var) try: if '.' in token: result = float(token) else: result = int(token) except: result = 0 target_namespace.put(var, result) return None def TOSTRING(self, namespace, argv): name = str(argv[0]) target_namespace = self.select_namespace(namespace, name) value = str(target_namespace.get(name)) target_namespace.put(name, value) def TONUMBER2(self, namespace, argv): token = str(argv[0]) try: if '.' in token: value = float(token) else: value = int(token) except: return 0 else: return value def TOSTRING2(self, namespace, argv): return str(argv[0]) def TOUPPER(self, namespace, argv): return str(argv[0]).upper() def TOLOWER(self, namespace, argv): return str(argv[0]).lower() def TOBINSTR(self, namespace, argv): try: i = int(argv[0]) except: return '' if i < 0: i = abs(i) numsin = '-' else: numsin = '' line = '' while i: mod = i % 2 i /= 2 line = ''.join((str(mod), line)) line = ''.join((numsin, line)) return line def TOHEXSTR(self, namespace, argv): try: return '{0:x}'.format(int(argv[0])) except: return '' def BINSTRTONUM(self, namespace, argv): try: return int(str(argv[0]), 2) except: return -1 def HEXSTRTONUM(self, namespace, argv): try: return int(str(argv[0]), 16) except: return -1 def ERASEVARIABLE(self, namespace, argv): var = str(argv[0]) target_namespace = self.select_namespace(namespace, var) target_namespace.remove(var) def STRLEN(self, namespace, argv): line = str(argv[0]) if len(argv) == 2: var = str(argv[1]) target_namespace = self.select_namespace(namespace, var) target_namespace.put(var, len(line)) return None else: return len(line) def STRSTR(self, namespace, argv): line = str(argv[0]) to_find = str(argv[1]) try: start = int(argv[2]) except: return -1 result = line.find(to_find, start) if len(argv) == 4: var = str(argv[3]) target_namespace = self.select_namespace(namespace, var) target_namespace.put(var, result) return None else: return result def SUBSTR(self, namespace, argv): line = str(argv[0]) try: start = int(argv[1]) bytes = int(argv[2]) except: return '' return line[start:start + bytes] def REPLACE(self, namespace, argv): line = str(argv[0]) old = str(argv[1]) new = str(argv[2]) return line.replace(old, new) def ERASE(self, namespace, argv): line = str(argv[0]) try: start = int(argv[1]) bytes = int(argv[2]) except: return '' return ''.join((line[:start], line[start + bytes:])) def INSERT(self, namespace, argv): line = str(argv[0]) try: start = int(argv[1]) except: return '' to_insert = str(argv[2]) if start < 0: start = 0 return ''.join((line[:start], to_insert, line[start:])) def MSTRLEN(self, namespace, argv): line = str(argv[0]) i = 0 count = 0 while i < len(line): if ord(line[i]) < 0x80: ## FIXME n = 1 else: n = 2 i += n count += 1 return count def MSTRSTR(self, namespace, argv): line = str(argv[0]) to_find = str(argv[1]) try: int(argv[2]) except: return -1 start = len(line) i = 0 count = 0 while i < len(line): if ord(line[i]) < 0x80: ## FIXME n = 1 else: n = 2 i += n count += 1 if count == int(argv[2]): start = i break result = line.find(to_find, start) if result != -1: result = self.MSTRLEN(namespace, [line[:result]]) return result def MSUBSTR(self, namespace, argv): line = str(argv[0]) try: int(argv[1]) int(argv[2]) except: return '' start = len(line) end = len(line) i = 0 count = 0 while i < len(line): if count == int(argv[1]): start = i if count == int(argv[1]) + int(argv[2]): end = i break if ord(line[i]) < 0x80: ## FIXME n = 1 else: n = 2 i += n count += 1 return line[start:end] def MERASE(self, namespace, argv): line = str(argv[0]) try: int(argv[1]) int(argv[2]) except: return '' start = len(line) end = len(line) i = 0 count = 0 while i < len(line): if count == int(argv[1]): start = i if count == int(argv[1]) + int(argv[2]): end = i break if ord(line[i]) < 0x80: ## FIXME n = 1 else: n = 2 i += n count += 1 return ''.join((line[:start], line[end:])) def MINSERT(self, namespace, argv): line = str(argv[0]) try: int(argv[1]) except: return '' to_insert = str(argv[2]) if int(argv[1]) < 0: start = 0 else: start = len(line) i = 0 count = 0 while i < len(line): if count == int(argv[1]): start = i break if ord(line[i]) < 0x80: ## FIXME n = 1 else: n = 2 i += n count += 1 return ''.join((line[:start], to_insert, line[start:])) def CUTSPACE(self, namespace, argv): return line_strip(str(argv[0])) def NAMETOVALUE(self, namespace, argv): if len(argv) == 2: var = str(argv[0]) name = str(argv[1]) else: name = str(argv[0]) target_namespace = self.select_namespace(namespace, name) value = target_namespace.get(name) if len(argv) == 2: target_namespace = self.select_namespace(namespace, var) target_namespace.put(var, value) return None else: return value def LETTONAME(self, namespace, argv): var = str(argv[0]) value = argv[1] if not var: return None target_namespace = self.select_namespace(namespace, var) target_namespace.put(var, value) return None def ARRAYSIZE(self, namespace, argv): if isinstance(argv[0], str): line = argv[0] if not line or line == '': value = 0 elif line.startswith('"') and line.endswith('"'): value = line.count(',') + 1 else: target_namespace = self.select_namespace(namespace, line) value = target_namespace.get_size(line) else: value = 0 if len(argv) == 2: var = str(argv[1]) target_namespace = self.select_namespace(namespace, var) target_namespace.put(var, value) return None else: return value def CALLBYNAME(self, namespace, argv): # dummy return None def FUNCTIONEX(self, namespace, argv): # FIXME # Ver.3 return None def SAORI(self, namespace, argv): # FIXME # Ver.3 return None def GETLASTERROR(self, namespace, argv): return self.errno def LOGGING(self, namespace, argv): if argv[0] is None: return None logfile = argv[0] line = ''.join(('> function ', str(argv[1]), u' : '.encode('EUC-JP'), str(argv[2]))) if argv[3] is not None: line = ''.join((line, ' = ')) if isinstance(argv[3], int) or isinstance(argv[3], float): line = ''.join((line, str(argv[3]))) else: line = ''.join((line, '"', str(argv[3]), '"')) line = ''.join((line, '\n')) logfile.write(line) logfile.write('\n') return None def RAND(self, namespace, argv): if not argv: return int(random.randrange(0, 100, 1)) else: try: int(argv[0]) except: return -1 return int(random.randrange(0, int(argv[0]), 1)) def ASC(self, namespace, argv): try: int(argv[0]) except: return '' index = int(argv[0]) if 0 <= index < 0x80: ## FIXME return chr(index) else: return ' ' def IASC(self, namespace, argv): if isinstance(argv[0], str): return -1 try: char = argv[0][0] except: return -1 return ord(char) def FLOOR(self, namespace, argv): try: return int(math.floor(float(argv[0]))) except: return -1 def CEIL(self, namespace, argv): try: return int(math.ceil(float(argv[0]))) except: return -1 def ROUND(self, namespace, argv): try: value = math.floor(float(argv[0]) + 0.5) except: return -1 return int(value) def ISINSIDE(self, namespace, argv): if argv[1] <= argv[0] <= argv[2]: return 1 else: return 0 def ISINTEGER(self, namespace, argv): if isinstance(argv[0], int): return 1 else: return 0 def ISREAL(self, namespace, argv): if isinstance(argv[0], float) or isinstance(argv[0], int): return 1 else: return 0 def ISFUNCTION(self, namespace, argv): if not isinstance(argv[0], str): return 0 elif self.aya.dic.get_function(argv[0]) is not None: return 1 elif self.aya.get_system_functions().exists(argv[0]): return 2 else: return 0 def SIN(self, namespace, argv): try: result = math.sin(float(argv[0])) except: return -1 return self.select_math_type(result) def COS(self, namespace, argv): try: result = math.cos(float(argv[0])) except: return -1 return self.select_math_type(result) def TAN(self, namespace, argv): try: result = math.tan(float(argv[0])) except: return -1 return self.select_math_type(result) def LOG(self, namespace, argv): try: float(argv[0]) except: return -1 if float(argv[0]) == 0: return 0 result = math.log(float(argv[0])) return self.select_math_type(result) def LOG10(self, namespace, argv): try: float(argv[0]) except: return -1 if float(argv[0]) == 0: return 0 result = math.log10(float(argv[0])) return self.select_math_type(result) def POW(self, namespace, argv): try: result = math.pow(float(argv[0]), float(argv[1])) except: return -1 return self.select_math_type(result) def SQRT(self, namespace, argv): try: float(argv[0]) except: return -1 if float(argv[0]) < 0.: return -1 else: result = math.sqrt(float(argv[0])) return self.select_math_type(result) def SETSEPARATOR(self, namespace, argv): name = str(argv[0]) separator = str(argv[1]) target_namespace = self.select_namespace(namespace, name) target_namespace.set_separator(name, separator) return None def REQ_COMMAND(self, namespace, argv): return self.aya.req_command def REQ_HEADER(self, namespace, argv): try: int(argv[0]) except: return '' if len(self.aya.req_key) > int(argv[0]): return self.aya.req_key[int(argv[0])] else: return '' def REQ_VALUE(self, namespace, argv): if isinstance(argv[0], int): name = self.REQ_HEADER(namespace, [argv[0]]) else: name = str(argv[0]) if name in self.aya.req_header: return self.aya.req_header[name] else: return '' def REQ_PROTOCOL(self, namespace, argv): return self.aya.req_protocol def LOADLIB(self, namespace, argv): dll = str(argv[0]) result = 0 if dll: if self.security.check_lib(dll): result = self.aya.saori_library.load(dll, self.aya.aya_dir) if result == 0: self.errno = 17 else: self.errno = 18 return result def UNLOADLIB(self, namespace, argv): if str(argv[0]): self.aya.saori_library.unload(str(argv[0])) return None def REQUESTLIB(self, namespace, argv): response = self.aya.saori_library.request( str(argv[0]), unicode(str(argv[1]), 'EUC-JP', 'ignore').encode('Shift_JIS', 'ignore')) ## FIXME header = response.splitlines() line = header.pop(0) self.saori_statuscode = '' self.saori_header = [] self.saori_value = {} self.saori_protocol = '' if line: line = line_strip(line) if ' ' in line: self.saori_protocol, self.saori_statuscode = [line_strip(x) for x in line.split(' ', 1)] for line in header: if ':' not in line: continue key, value = [line_strip(x) for x in line.split(':', 1)] if key: self.saori_header.append(key) self.saori_value[key] = value return None def LIB_STATUSCODE(self, namespace, argv): return self.saori_statuscode def LIB_HEADER(self, namespace, argv): try: int(argv[0]) except: return '' result = '' header_list = self.saori_header if header_list and int(argv[0]) < len(header_list): result = header_list[int(argv[0])] return result def LIB_VALUE(self, namespace, argv): result = '' if isinstance(argv[0], int): header_list = self.saori_header if header_list and int(argv[0]) < len(header_list): key = header_list[int(argv[0])] else: key = str(argv[0]) if key in self.saori_value: result = self.saori_value[key] return result def LIB_PROTOCOL(self, namespace, argv): return self.aya.saori_protocol def FOPEN(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) accessmode = str(argv[1]) result = 0 path = os.path.join(self.aya.aya_dir, filename) if self.security.check_path(path, accessmode[0]): norm_path = os.path.normpath(path) if norm_path in self.aya.filelist: result = 2 else: try: self.aya.filelist[norm_path] = open(path, accessmode[0]) except: self.errno = 257 else: result = 1 else: self.errno = 258 return result def FCLOSE(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) norm_path = os.path.normpath(path) if norm_path in self.aya.filelist: self.aya.filelist[norm_path].close() del self.aya.filelist[norm_path] return None def FREAD(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) norm_path = os.path.normpath(path) result = -1 if norm_path in self.aya.filelist: f = self.aya.filelist[norm_path] result = unicode( f.readline(), 'Shift_JIS', 'ignore').encode('EUC-JP', 'ignore') ## FIXME if not result: result = -1 elif result.endswith('\r\n'): result = result[:-2] elif result.endswith('\n'): result = result[:-1] return result def FWRITE(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) norm_path = os.path.normpath(path) data = ''.join((str(argv[1]), '\n')) if norm_path in self.aya.filelist: f = self.aya.filelist[norm_path] data = unicode( data, 'EUC-JP', 'ignore').encode('Shift_JIS', 'ignore') ## FIXME f.write(data) return None def FWRITE2(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) norm_path = os.path.normpath(path) data = str(argv[1]) if norm_path in self.aya.filelist: f = self.aya.filelist[norm_path] data = unicode( data, 'EUC-JP', 'ignore').encode('Shift_JIS', 'ignore') ## FIXME f.write(data) return None def FCOPY(self, namespace, argv): src = get_normalized_path(str(argv[0]), encode=0) head, tail = os.path.split(src) dst = ''.join((get_normalized_path(str(argv[1]), encode=0), '/', tail)) src_path = os.path.join(self.aya.aya_dir, src) dst_path = os.path.join(self.aya.aya_dir, dst) result = 0 if not os.path.isfile(src_path): self.errno = 260 elif not os.path.isdir(dst_path): self.errno = 261 elif self.security.check_path(src_path, 'r') and \ self.security.check_path(dst_path): try: shutil.copyfile(src_path, dst_path) except: self.errno = 262 else: result = 1 else: self.errno = 263 return result def FMOVE(self, namespace, argv): src = get_normalized_path(str(argv[0]), encode=0) head, tail = os.path.split(src) dst = ''.join((get_normalized_path(str(argv[1]), encode=0), '/', tail)) src_path = os.path.join(self.aya.aya_dir, src) dst_path = os.path.join(self.aya.aya_dir, dst) result = 0 head, tail = os.path.split(dst_path) if not os.path.isfile(src_path): self.errno = 265 elif not os.path.isdir(head): self.errno = 266 elif self.security.check_path(src_path) and \ self.security.check_path(dst_path): try: os.rename(src_path, dst_path) except: self.errno = 267 else: result = 1 else: self.errno = 268 return result def FDELETE(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) result = 0 if not os.path.isfile(path): self.errno = 270 elif self.security.check_path(path): try: os.remove(path) except: self.errno = 271 else: result = 1 else: self.errno = 272 return result def FRENAME(self, namespace, argv): src = get_normalized_path(str(argv[0]), encode=0) dst = get_normalized_path(str(argv[1]), encode=0) src_path = os.path.join(self.aya.aya_dir, src) dst_path = os.path.join(self.aya.aya_dir, dst) result = 0 head, tail = os.path.split(dst_path) if not os.path.exists(src_path): self.errno = 274 elif not os.path.isdir(head): self.errno = 275 elif self.security.check_path(dst_path): try: os.rename(src_path, dst_path) except: self.errno = 276 else: result = 1 else: self.errno = 277 return result def FSIZE(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) size = -1 if not os.path.exists(path): self.errno = 279 elif self.security.check_path(path, 'r'): try: size = os.path.getsize(path) except: self.errno = 280 else: self.errno = 281 return size def MKDIR(self, namespace, argv): dirname = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, dirname) result = 0 head, tail = os.path.split(path) if not os.path.isdir(head): self.errno = 283 elif self.security.check_path(path): try: os.mkdir(path, 0755) except: self.errno = 284 else: result = 1 else: self.errno = 285 return result def RMDIR(self, namespace, argv): dirname = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, dirname) result = 0 if not os.path.isdir(path): self.errno = 287 elif self.security.check_path(path): try: os.rmdir(path) except: self.errno = 288 else: result = 1 else: self.errno = 289 return result def FENUM(self, namespace, argv): if len(argv) >= 2: separator = str(argv[1]) else: separator = ',' dirname = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, dirname) filelist = [] if self.security.check_path(path, 'r'): try: filelist = os.listdir(path) except: self.errno = 291 else: self.errno = 292 result = '' for index in range(len(filelist)): path = os.path.join(self.aya.aya_dir, dirname, filelist[index]) if os.path.isdir(path): result = ''.join((result, '\\')) result = ''.join((result, filelist[index])) if index != len(filelist) - 1: result = ''.join((result, separator)) return result def select_math_type(self, value): if math.floor(value) == value: return int(value) else: return value def select_namespace(self, namespace, name): if name.startswith('_'): return namespace else: return self.aya.get_global_namespace() class AyaNamespace(object): def __init__(self, aya, parent=None): self.aya = aya self.parent = parent self.table = {} def put(self, name, content, index=None): if self.parent is not None and self.parent.exists(name): self.parent.put(name, content, index) elif index is None: if not self.exists(name): self.table[name] = AyaVariable(name) self.table[name].put(content) elif self.exists(name) and index >=0: self.table[name].put(content, index) else: pass # ERROR def get(self, name, index=None): if name in self.table: return self.table[name].get(index) elif self.parent is not None and self.parent.exists(name): return self.parent.get(name, index) else: return None def set_separator(self, name, separator): if self.parent is not None and self.parent.exists(name): self.parent.set_separator(name, separator) elif name in self.table: self.table[name].set_separator(separator) else: pass # ERROR def get_size(self, name): if name in self.table: return self.table[name].get_size() elif self.parent is not None and self.parent.exists(name): return self.parent.get_size(name) else: return 0 def remove(self, name): # only works with local table if name in self.table: del self.table[name] def exists(self, name): result = name in self.table or \ (self.parent is not None and self.parent.exists(name)) return result 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 = re.compile('res_reference\d+$') def reset_res_reference(self): for key in self.table.keys(): if self.__re_res.match(key): del self.table[key] def get(self, name, index=None): t = time.localtime(time.time()) past = time.time() - self.aya.get_boot_time() if name == 'year': result = t[0] elif name == 'month': result = t[1] elif name == 'day': result = t[2] elif name == 'weekday': result = (t[6] + 1) % 7 elif name == 'hour': result = t[3] elif name == '12hour': result = t[3] % 12 elif name == 'ampm': if t[3] >= 12: result = 1 # pm else: result = 0 # am elif name == 'minute': result = t[4] elif name == 'second': result = t[5] elif name == 'systemuptickcount': result = int(past * 1000.0) elif name == 'systemuptime': result = int(past) elif name == 'systemuphour': result = int(past / 60.0 / 60.0) elif name == 'systemupminute': result = int(past / 60.0) % 60 elif name == 'systemupsecond': result = int(past) % 60 elif name in ['memoryload', 'memorytotalphys', 'memoryavailphys', 'memorytotalvirtual', 'memoryavailvirtual']: result = 0 # FIXME else: result = AyaNamespace.get(self, name, index) return result def exists(self, name): if name in self.__SYS_VARIABLES: return 1 else: return AyaNamespace.exists(self, name) def load_database(self, aya): ## FIXME: charset try: with open(aya.dbpath) as f: line = f.readline() if not line.startswith('# Format: v1.0') and \ not line.startswith('# Format: v1.1'): return 1 for line in f: comma = line.find(',') if comma >= 0: key = line[:comma] else: continue value = line[comma + 1:].strip() comma = find_not_quoted(value, ',') if comma >= 0: separator = value[comma + 1:].strip() separator = separator[1:-1] value = value[:comma].strip() value = value[1:-1] self.put(key, str(value)) self.table[key].set_separator(str(separator)) elif value.startswith('"'): # Format: v1.0 value = value[1:-1] self.put(key, str(value)) elif value != 'None': if '.' in value: self.put(key, float(value)) else: self.put(key, int(value)) else: pass except: return 1 return 0 def save_database(self): try: with open(self.aya.dbpath, 'w') as f: f.write('# Format: v1.1\n') ## FIXME: 1.2 - UTF-8 for key in self.table.keys(): line = self.table[key].dump() if line is not None: f.write(''.join((line, '\n'))) except IOError: logging.debug('aya.py: cannot write database (ignored)') return class AyaStatement(object): __SPECIAL_CHARS = '=+-*/<>|&!:' def __init__(self, line): self.n_tokens = 0 self.tokens = [] self.position_of_next_token = 0 self.tokenize(line) def tokenize(self, line): token_startpoint = 0 block_nest_level = 0 length = len(line) i = 0 while i < length: c = line[i] if c == '(': block_nest_level += 1 i += 1 elif c == ')': block_nest_level -= 1 i += 1 elif c == '"': if block_nest_level == 0: self.append_unless_empty(line[token_startpoint:i].strip()) token_startpoint = i position = i while position < length - 1: position += 1 if line[position] == '"': break i = position if block_nest_level == 0: self.tokens.append(line[token_startpoint:position + 1]) token_startpoint = position + 1 i += 1 elif block_nest_level == 0 and (c == ' ' or c == '\t'): self.append_unless_empty(line[token_startpoint:i].strip()) i += 1 token_startpoint = i elif block_nest_level == 0 and line[i:i + 2] == u' '.encode('EUC-JP'): self.append_unless_empty(line[token_startpoint:i].strip()) i += 2 token_startpoint = i elif block_nest_level == 0 and \ line[i:].strip()[:4] == '_in_': self.append_unless_empty(line[token_startpoint:i].strip()) self.tokens.append('_in_') i += 4 token_startpoint = i elif block_nest_level == 0 and \ line[i:].strip()[:5] == '!_in_': self.append_unless_empty(line[token_startpoint:i].strip()) self.tokens.append('!_in_') i += 5 token_startpoint = i elif block_nest_level == 0 and c in self.__SPECIAL_CHARS: self.append_unless_empty(line[token_startpoint:i].strip()) ope_list = [':=', '+=', '-=', '*=', '/=', '%=', '<=', '>=', '==', '!=', '&&', '||', '+:=', '-:=', '*:=', '/:=', '%:='] if line[i:i + 2] in ope_list: self.tokens.append(line[i:i + 2]) i += 2 token_startpoint = i elif line[i:i + 3] in ope_list: self.tokens.append(line[i:i + 3]) i += 3 token_startpoint = i else: self.tokens.append(line[i:i + 1]) i += 1 token_startpoint = i else: i += 1 self.append_unless_empty(line[token_startpoint:].strip()) self.n_tokens = len(self.tokens) def append_unless_empty(self, token): if token: self.tokens.append(token) def has_more_tokens(self): return (self.position_of_next_token < self.n_tokens) def countTokens(self): return self.n_tokens def next_token(self): if not self.has_more_tokens(): return None result = self.tokens[self.position_of_next_token] self.position_of_next_token += 1 return result class AyaVariable(object): __TYPE_STRING = 0 __TYPE_INT = 1 __TYPE_REAL = 2 __TYPE_ARRAY = 3 def __init__(self, name): self.name = name self.line = '' self.separator = ',' self.type = None self.array = [] def set_separator(self, separator): if self.type != self.__TYPE_STRING: return self.separator = separator self.reset() def reset(self): if self.type != self.__TYPE_STRING: return self.position = 0 self.is_empty = 0 self.array = [] while not self.is_empty: separator_position = self.line.find(self.separator, self.position) if separator_position == -1: token = self.line[self.position:] self.is_empty = 1 else: token = self.line[self.position:separator_position] self.position = separator_position + len(self.separator) self.array.append(token) def get_size(self): return len(self.array) def get(self, index=None): if 0 <= index < len(self.array): value = self.array[index] if self.type == self.__TYPE_STRING: return str(value) elif self.type == self.__TYPE_INT: return int(value) elif self.type == self.__TYPE_REAL: return float(value) elif self.type == self.__TYPE_ARRAY: return value else: return None # should not reach here elif index is None: if self.type == self.__TYPE_STRING: return str(self.line) elif self.type == self.__TYPE_INT: return int(self.line) elif self.type == self.__TYPE_REAL: return float(self.line) else: return '' else: return '' def put(self, value, index=None): if index is None: self.line = str(value) if isinstance(value, str): self.type = self.__TYPE_STRING elif isinstance(value, int): self.type = self.__TYPE_INT elif isinstance(value, float): self.type = self.__TYPE_REAL elif isinstance(value, list): self.type = self.__TYPE_ARRAY self.array = value self.reset() elif index < 0: pass else: if self.type == self.__TYPE_STRING: self.line = '' for i in range(len(self.array)): if i == index: self.line = ''.join((self.line, str(value))) else: self.line = ''.join((self.line, self.array[i])) if i != len(self.array)-1: self.line = ''.join((self.line, self.separator)) if index >= len(self.array): for i in range(len(self.array), index + 1): if i == index: self.line = ''.join((self.line, self.separator, str(value))) else: self.line = ''.join((self.line, self.separator, '')) self.reset() elif self.type == self.__TYPE_ARRAY: if 0 <= index < len(self.array): self.array[index] = value else: pass # ERROR def dump(self): line = None if self.type == self.__TYPE_STRING: line = '{0}, "{1}", "{2}"'.format(self.name, self.line, self.separator) elif self.type != self.__TYPE_ARRAY: line = '{0}, {1}'.format(self.name, self.line) else: pass return line class AyaArgument(object): def __init__(self, line): self.line = line.strip() self.length = len(self.line) self.current_position = 0 def has_more_tokens(self): return (self.current_position != -1 and \ self.current_position < self.length) def next_token(self): if not self.has_more_tokens(): return None startpoint = self.current_position self.current_position = self.position_of_next_token() if self.current_position == -1: token = self.line[startpoint:] else: token = self.line[startpoint:self.current_position-1] return token.strip() def position_of_next_token(self): locked = 1 position = self.current_position parenthesis_nest_level = 0 while position < self.length: c = self.line[position] if c == '"': if not locked: return position while position < self.length-1: position += 1 if self.line[position] == '"': break elif c == '(': parenthesis_nest_level += 1 elif c == ')': parenthesis_nest_level -= 1 elif c == ',': if parenthesis_nest_level == 0: locked = 0 else: if not locked: return position position += 1 return -1 class AyaSaoriLibrary(object): def __init__(self, saori, top_dir): self.saori_list = {} self.saori = saori def load(self, name, top_dir): result = 0 if self.saori and name not in self.saori_list: module = self.saori.request(name) if module: self.saori_list[name] = module if name in self.saori_list: result = self.saori_list[name].load(top_dir) return result def unload(self, name=None): if name: if name in self.saori_list: self.saori_list[name].unload() del self.saori_list[name] else: for key in self.saori_list.keys(): self.saori_list[key].unload() return None def request(self, name, req): result = '' # FIXME if name and name in self.saori_list: result = self.saori_list[name].request(req) return result def test(top_dir, function, argv): aya = Shiori('aya.dll') aya.load(top_dir) result = aya.dic.get_function(function).call(argv) print str(result) if __name__ == '__main__': USAGE= 'Usage(1): aya.py []\n' \ 'Usage(2): aya.py encrypt ' if len(sys.argv) < 3: print USAGE elif sys.argv[1] == 'encrypt': if len(sys.argv) != 4: print USAGE else: path = os.path.join(os.curdir, sys.argv[2]) with open(path) as inputf: path = os.path.join(os.curdir, sys.argv[3]) with open(path, 'w') as outputf: while 1: c = inputf.read(1) if c == '': break outputf.write(encrypt_char(c)) else: test(sys.argv[1], sys.argv[2], sys.argv[3:]) ninix-aya-4.3.9/lib/ninix/dll/aya5.py000066400000000000000000004030771172114553600173230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # aya5.py - an aya.dll(Ver.5) compatible Shiori module for ninix # Copyright (C) 2002-2012 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: # - 文字列内埋め込み要素の展開の動作が非互換. # - システム関数: # - たくさん. import os import sys import logging import random import time import math import shutil import re from ninix.home import get_normalized_path class AyaError(ValueError): pass def encrypt_char(char): c = ord(char) j = 0 while j < 3: msb = c & 0x80 c <<= 1 c &= 0xff if msb: c |= 0x01 else: c &= 0xfe j += 1 c ^= 0xd2 return chr(c) def decrypt_char(char): c = ord(char) c ^= 0xd2 j = 0 while j < 3: lsb = c & 0x01 c >>= 1 if lsb: c |= 0x80 else: c &= 0x7f j += 1 return chr(c) def decrypt_readline(f): line = '' while 1: c = f.read(1) if c == '': break line = ''.join((line, decrypt_char(c))) if line.endswith(chr(10)) or \ line.endswith(''.join((chr(13), chr(10)))): break return line def find_not_quoted(line, token): position = 0 while 1: pos_new = line.find(token, position) if pos_new < 0: break elif pos_new == 0: break position = line.find('"', position) if 0 <= position < pos_new: position += 1 while position < len(line) - 1: if line[position] == '"': position += 1 break else: position += 1 continue else: break return pos_new def find_comment(line): if line.startswith('//'): return 0, len(line) start = len(line) # not len(line) - 1 end = -1 for token in ['//', '/*']: pos_new = find_not_quoted(line, token) if 0 <= pos_new < start: start = pos_new if token == '/*': end = find_not_quoted(line, '*/') if end >= 0: end += 2 else: end = len(line) if start == len(line): start = -1 return start, end def get_aya_version(filelist): if not filelist: return 0 dic_files = filelist for filename in dic_files: if filename.lower().endswith('_shiori3.dic'): # XXX with open(filename) as f: for line in f: try: line = unicode(line, 'Shift_JIS') # XXX if line.find(unicode('for 文 version 4', 'utf-8')) > 0: return 4 elif line.find('for AYA5') > 0: return 5 except: return 5 else: return 3 def find_dict(aya_dir, f): comment = 0 dic_files = [] for line in f: line = unicode(line, 'Shift_JIS', 'ignore') # XXX if comment: end = find_not_quoted(line, '*/') if end < 0: continue else: line = line[end + 2:] comment = 0 while 1: start, end = find_comment(line) if start < 0: break if end < 0: comment = 1 line = line[:start] break line = ' '.join((line[:start], line[end:])) line = line.strip() if not line: continue if ',' not in line: continue key, value = [x.strip() for x in line.split(',', 1)] if key == 'dic': filename = get_normalized_path(value, encode=0) path = os.path.join(aya_dir, filename) dic_files.append(path) return dic_files def check_version(top_dir, dll_name): filename = None if os.path.isfile(os.path.join(top_dir, 'aya.txt')): filename = os.path.join(top_dir, 'aya.txt') elif os.path.isfile(os.path.join(top_dir, 'yaya.txt')): return 6 # XXX: YAYA elif dll_name is not None and \ os.path.isfile(os.path.join(top_dir, ''.join((dll_name[:-3], 'txt')))): filename = os.path.join(top_dir, ''.join((dll_name[:-3], 'txt'))) if filename is not None: with open(filename) as f: version = get_aya_version(find_dict(top_dir, f)) else: version = 0 return version class Shiori(object): __AYA_TXT = 'aya.txt' __DBNAME = 'aya_variable.cfg' def __init__(self, dll_name): self.dll_name = dll_name if dll_name is not None: self.__AYA_TXT = ''.join((dll_name[:-3], 'txt')) self.__DBNAME = ''.join((dll_name[:-4], '_variable.cfg')) self.saori = None self.dic_files = [] def use_saori(self, saori): self.saori = saori def find(self, top_dir, dll_name): result = 0 version = check_version(top_dir, dll_name) if version == 5: result = 200 return result def show_description(self): logging.info( 'Shiori: AYA5 compatible module for ninix\n' ' Copyright (C) 2002-2012 by Shyouzou Sugitani\n' ' Copyright (C) 2002, 2003 by MATSUMURA Namihiko') def reset(self): self.boot_time = time.time() self.aitalk = 0 self.dic_files = [] self.dic = AyaDictionary(self) self.global_namespace = AyaGlobalNamespace(self) self.system_functions = AyaSystemFunctions(self) self.logfile = None self.filelist = {} def load(self, aya_dir=None): self.aya_dir = aya_dir self.dbpath = os.path.join(self.aya_dir, self.__DBNAME) self.saori_library = AyaSaoriLibrary(self.saori, self.aya_dir) self.reset() try: path = os.path.join(self.aya_dir, self.__AYA_TXT) with open(path) as aya_txt: self.load_aya_txt(aya_txt) except IOError: logging.debug('cannot read aya.txt') return 0 except AyaError as error: logging.debug(error) return 0 self.global_namespace.load_database(self) # default setting if not self.global_namespace.exists('log'): self.global_namespace.put('log', '') for path in self.dic_files: basename, ext = os.path.splitext(path) ext = ext.lower() if ext == '.ayc': encrypted = 1 else: encrypted = 0 try: with open(path, 'r') as dicfile: self.dic.load(dicfile, encrypted) except: logging.debug('cannnot read {0}'.format(path)) continue func = self.dic.get_function('load') if func: func.call([self.aya_dir.replace('/', '\\')]) return 1 def load_aya_txt(self, f): comment = 0 self.charset = 'Shift_JIS' # default for line in f: line = unicode(line, self.charset, 'ignore') if comment: end = find_not_quoted(line, '*/') if end < 0: continue else: line = line[end + 2:] comment = 0 while 1: start, end = find_comment(line) if start < 0: break if end < 0: comment = 1 line = line[:start] break line = ' '.join((line[:start], line[end:])) line = line.strip() if not line: continue if ',' not in line: continue key, value = [x.strip() for x in line.split(',', 1)] self.evaluate_config(key, value) def evaluate_config(self, key, value): if key == 'charset': if value in ['Shift_JIS', 'ShiftJIS', 'SJIS']: self.charset = 'Shift_JIS' elif value == 'UTF-8': self.charset = 'UTF-8' else: # default and error self.charset = 'Shift_JIS' elif key == 'dic': filename = get_normalized_path(value, encode=0) path = os.path.join(self.aya_dir, filename) self.dic_files.append(path) elif key == 'msglang': pass ## FIXME elif key == 'log': assert isinstance(value, (str, unicode)) if isinstance(value, unicode): filename = value.encode('utf-8', 'ignore') else: filename = value path = os.path.join(self.aya_dir, filename) try: f = open(path, 'w') except: logging.debug('cannnot open {0}'.format(path)) else: if self.logfile: self.logfile.close() self.logfile = f self.global_namespace.put('log', value) elif key == 'iolog': pass ## FIXME elif key == 'fncdepth': pass ## FIXME def get_dictionary(self): return self.dic def get_ghost_dir(self): return self.aya_dir def get_global_namespace(self): return self.global_namespace def get_system_functions(self): return self.system_functions def get_boot_time(self): return self.boot_time def unload(self): func = self.dic.get_function('unload') if func: func.call([]) self.global_namespace.save_database() self.saori_library.unload() if self.logfile is not None: self.logfile.close() for key in self.filelist.keys(): self.filelist[key].close() # SHIORI API def request(self, req_string): result = '' func = self.dic.get_function('request') if func: result = func.call([unicode(req_string, self.charset, 'ignore')]) if result is None: result = '' return result.encode(self.charset, 'ignore') class AyaDictionary(object): def __init__(self, aya): self.aya = aya self.functions = {} self.global_macro = {} def get_function(self, name): return self.functions.get(name, None) def load(self, f, encrypted): all_lines = [] local_macro = {} logical_line = '' comment = 0 while 1: if encrypted: line = decrypt_readline(f) else: line = f.readline() line = unicode(line, self.aya.charset, 'ignore') if not line: break # EOF if comment: end = find_not_quoted(line, '*/') if end < 0: continue else: line = line[end + 2:] comment = 0 line = line.strip() if not line: continue if line.endswith('/') and \ not line.endswith('*/') and not line.endswith('//'): logical_line = ''.join((logical_line, line[:-1])) else: logical_line = ''.join((logical_line, line)) while 1: start, end = find_comment(logical_line) if start < 0: break if end < 0: comment = 1 logical_line = logical_line[:start] break logical_line = ''.join((logical_line[:start], ' ', logical_line[end:])) logical_line = logical_line.strip() if not logical_line: continue buf = logical_line # preprosess if buf.startswith('#'): buf = buf[1:].strip() for (tag, target) in [('define', local_macro), ('globaldefine', self.global_macro)]: if buf.startswith(tag): buf = buf[len(tag):].strip() i = 0 while i < len(buf): if buf[i] == ' ' or buf[i] == '\t' or \ buf[i:i + 2] == unicode(' ', 'utf-8'): key = buf[:i].strip() target[key] = buf[i:].strip() break i += 1 break logical_line = '' # reset continue for macro in [local_macro, self.global_macro]: logical_line = self.preprosess(macro, logical_line) # multi statement list_lines = self.split_line(logical_line.strip()) if list_lines: all_lines.extend(list_lines) logical_line = '' # reset self.evaluate_lines(all_lines, os.path.split(f.name)[1]) def split_line(self, line): lines = [] while 1: if not line: break pos = len(line) # not len(line) - 1 token = '' for x in ['{', '}', ';']: pos_new = find_not_quoted(line, x) if 0 <= pos_new < pos: pos = pos_new token = x new = line[:pos].strip() line = line[pos + len(token):].strip() if new: lines.append(new) if token not in ['', ';']: lines.append(token) return lines def preprosess(self, macro, line): for key, value in macro.items(): line = line.replace(key, value) return line __SPECIAL_CHARS = [r']', r'(', r')', r'[', r'+', r'-', r'*', r'/', r'=', r':', r';', r'!', r'{', r'}', r'%', r'&', r'#', r'"', r'<', r'>', r',', r'?'] def evaluate_lines(self, lines, file_name): prev = None name = None function = [] option = None block_nest = 0 for i in range(len(lines)): line = lines[i] if line == '{': if name is not None: function.append(line) block_nest += 1 else: if prev is None: logging.debug( 'syntax error in {0}: unbalanced "{" at ' 'the top of file'.format(file_name)) else: logging.debug( 'syntax error in {0}: unbalanced "{" at ' 'the bottom of function "{1}"'.format(file_name, prev)) elif line == '}': if name is not None: block_nest -= 1 function.append(line) if block_nest == 0: self.functions[name] = AyaFunction(self, name, function, option) # reset prev = name name = None function = [] option = None else: if prev is None: logging.debug( 'syntax error in {0}: unbalanced "}" at ' 'the top of file'.format(file_name)) else: logging.debug( 'syntax error in {0}: unbalanced "}" at ' 'the bottom of function "{1}"'.format(file_name, prev)) block_nest = 0 elif name is None: if ':' in line: name, option = [x.strip() for x in line.split(':', 1)] else: name = line for char in self.__SPECIAL_CHARS: if char in name: logging.debug( 'illegal function name "{0}" in {1}'.format( name, file_name)) function = [] else: if name is not None and block_nest > 0: function.append(line) else: logging.debug('syntax error in {0}: {1}'.format(file_name, line)) class AyaFunction(object): __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 = re.compile('[-+]?\d+(\.\d*)$') __re_d = re.compile('[-+]?\d+$') __re_b = re.compile('[-+]?0[bB][01]+$') __re_x = re.compile('[-+]?0[xX][\dA-Fa-f]+$') __re_if = re.compile('if\s') __re_others = re.compile('others\s') __re_elseif = re.compile('elseif\s') __re_while = re.compile('while\s') __re_for = re.compile('for\s') __re_foreach = re.compile('foreach\s') __re_switch = re.compile('switch\s') __re_case = re.compile('case\s') __re_when = re.compile('when\s') __re_parallel = re.compile('parallel\s') __SPECIAL_CHARS = [r']', r'(', r')', r'[', r'+', r'-', r'*', r'/', r'=', r':', r';', r'!', r'{', r'}', r'%', r'&', r'#', r'"', r'<', r'>', r',', r'?'] def __init__(self, dic, name, lines, option): self.dic = dic self.name = name self.status = self.__CODE_NONE self.lines = self.parse(lines) if option == 'nonoverlap': self.nonoverlap = [[], [], []] else: self.nonoverlap = None if option == 'sequential': self.sequential = [[], []] else: self.sequential = None ## FIXME: void, array def parse(self, lines): result = [] i = 0 while i < len(lines): line = lines[i] if line == '--': result.append([self.__TYPE_DECISION, []]) elif line == 'return': result.append([self.__TYPE_RETURN, []]) elif line == 'break': result.append([self.__TYPE_BREAK, []]) elif line == 'continue': result.append([self.__TYPE_CONTINUE, []]) elif line == '{': inner_func = [] i, inner_func = self.get_block(lines, i) result.append([self.__TYPE_BLOCK, self.parse(inner_func)]) elif self.__re_if.match(line): inner_blocks = [] while 1: current_line = lines[i] if self.__re_if.match(current_line): condition = self.parse_(current_line[2:].strip()) elif self.__re_elseif.match(current_line): condition = self.parse_(current_line[6:].strip()) else: condition = [self.__TYPE_CONDITION, None] inner_block = [] i, inner_block = self.get_block(lines, i + 1) if condition is None: inner_blocks = [] break entry = [] entry.append(condition) entry.append(self.parse(inner_block)) inner_blocks.append(entry) if i + 1 >= len(lines): break next_line = lines[i + 1] if not self.__re_elseif.match(next_line) and \ next_line != 'else': break i = i + 1 if inner_blocks: result.append([self.__TYPE_IF, inner_blocks]) elif self.__re_while.match(line): condition = self.parse_(line[5:].strip()) inner_block = [] i, inner_block = self.get_block(lines, i + 1) result.append([self.__TYPE_WHILE, [condition, self.parse(inner_block)]]) elif self.__re_for.match(line): init = self.parse([line[3:].strip()]) ## FIXME(?) condition = self.parse_(lines[i + 1]) reset = self.parse([lines[i + 2]]) ## FIXME(?) inner_block = [] i, inner_block = self.get_block(lines, i + 3) if condition is not None: result.append([self.__TYPE_FOR, [[init, condition, reset], self.parse(inner_block)]]) elif self.__re_foreach.match(line): name = line[7:].strip() var = lines[i + 1] i, inner_block = self.get_block(lines, i + 2) result.append([self.__TYPE_FOREACH, [[name, var], self.parse(inner_block)]]) elif self.__re_switch.match(line): index = self.parse_(line[6:].strip()) inner_block = [] i, inner_block = self.get_block(lines, i + 1) result.append([self.__TYPE_SWITCH, [index, self.parse(inner_block)]]) elif self.__re_case.match(line): left = self.parse_(line[4:].strip()) i, block = self.get_block(lines, i + 1) inner_blocks = [] j = 0 while 1: current_line = block[j] if self.__re_when.match(current_line): right = current_line[4:].strip() else: # 'others' right = None inner_block = [] j, inner_block = self.get_block(block, j + 1) if right is not None: argument = AyaArgument(right) while argument.has_more_tokens(): entry = [] right = argument.next_token() tokens = AyaStatement(right).tokens if tokens[0] in ['-', '+']: value_min = self.parse_statement([tokens.pop(0), tokens.pop(0)]) ## FIXME: parse_ else: value_min = self.parse_statement([tokens.pop(0)]) ## FIXME: parse_ value_max = value_min if tokens: if tokens[0] != '-': logging.debug( 'syntax error in function ' '"{0}": when {1}'.format(self.name, right)) continue else: tokens.pop(0) if len(tokens) > 2 or \ (len(tokens) == 2 and \ tokens[0] not in ['-', '+']): logging.debug( 'syntax error in function ' '"{0}": when {1}'.format(self.name, right)) continue else: value_max = self.parse_statement(tokens) ## FIXME: parse_ entry.append([value_min, value_max]) entry.append(self.parse(inner_block)) inner_blocks.append(entry) else: entry = [] entry.append(right) entry.append(self.parse(inner_block)) inner_blocks.append(entry) if j + 1 == len(block): break next_line = block[j + 1] if not self.__re_when.match(next_line) and \ next_line != 'others': break j += 1 result.append([self.__TYPE_CASE, [left, inner_blocks]]) elif self.__re_parallel.match(line): ## FIXME pass else: result.append([self.__TYPE_FORMULA, self.parse_(line)]) ## FIXME i += 1 result.append([self.__TYPE_DECISION, []]) return result def find_close_(self, token_open, tokens, position): nest = 0 current = position if token_open == '[': token_close = ']' elif token_open == '(': token_close = ')' while tokens[current:].count(token_close) > 0: pos_new = tokens.index(token_close, current) if pos_new == 0: break nest = tokens[position:pos_new].count(token_open) - tokens[position:pos_new].count(token_close) # - 1 if nest > 0: current = pos_new + 1 else: current = pos_new break return current def iter_position(self, tokens, ope_list, reverse=0): position = 0 len_tokens = len(tokens) while 1: new_pos = len_tokens if reverse: tokens.reverse() for ope in ope_list: if ope in tokens[position:]: temp_pos = tokens.index(ope, position) new_pos = min(new_pos, temp_pos) if reverse: tokens.reverse() position = new_pos if position >= len_tokens: break else: if reverse: yield len_tokens - position else: yield position position += 1 def find_position(self, tokens, ope_list, position, reverse=0): len_tokens = len(tokens) new_pos = len_tokens if reverse: tokens.reverse() for ope in ope_list: if ope in tokens[position:]: temp_pos = tokens.index(ope, position) new_pos = min(new_pos, temp_pos) if reverse: tokens.reverse() position = new_pos if position >= len_tokens: return -1 else: if reverse: return len_tokens - 1 - position else: return position def get_left_(self, tokens, position): position -= 1 left = tokens.pop(position) if isinstance(left, (str, unicode)): # not processed left = self.new_parse([left]) return left def get_right_(self, tokens, position): right = tokens.pop(position) if isinstance(right, (str, unicode)): # not processed if right in ['-', '+']: right = self.new_parse([right, tokens.pop(position)]) else: right = self.new_parse([right]) return right def new_parse(self, tokens): ## FIXME if len(tokens) == 0: ## FIXME return [] position = self.find_position(tokens, ['(', '['], 0) while position >= 0: pos_end = self.find_close_(tokens[position], tokens, position + 1) temp = [] token_open = tokens.pop(position) # open for i in range(pos_end - position - 1): temp.append(tokens.pop(position)) tokens.pop(position) # close if token_open == '[': # array name = tokens.pop(position - 1) tokens.insert(position - 1, [self.__TYPE_ARRAY, [name, [self.__TYPE_FORMULA, self.new_parse(temp)]]]) else: ope_list = ['!', '++', '--', '*', '/', '%', '+', '-', '&', '==', '!=', '>=', '<=', '>', '<', '_in_', '!_in_', '&&', '||', '=', ':=', '+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:=', ',=', ','] ## FIXME if position == 0 or \ tokens[position - 1] in ope_list: # FORMULA tokens.insert(position, [self.__TYPE_FORMULA, self.new_parse(temp)]) else: # should be function name = tokens.pop(position - 1) if self.dic.aya.get_system_functions().exists(name): tokens.insert(position - 1, [self.__TYPE_SYSTEM_FUNCTION, [name, [self.new_parse(temp)]]]) ## CHECK: arguments else: tokens.insert(position - 1, [self.__TYPE_FUNCTION, [name, [self.new_parse(temp)]]]) ## CHECK: arguments position = self.find_position(tokens, ['(', '['], 0) for position in self.iter_position(tokens, ['!']): tokens.pop(position) right = self.get_right_(tokens, position) tokens.insert(position, [self.__TYPE_CONDITION, [None, [self.__TYPE_OPERATOR, '!'], right]]) for position in self.iter_position(tokens, ['++', '--']): if tokens.pop(position) == '++': type_ = self.__TYPE_INC else: type_ = self.__TYPE_DEC var = tokens.pop(position - 1) if isinstance(var, (str, unicode)): # not processed right = self.new_parse([var]) ## FIXME tokens.insert(position - 1, [type_, var]) position = self.find_position(tokens, ['*', '/', '%'], 0, reverse=1) while position >= 0: ope = [self.__TYPE_OPERATOR, tokens.pop(position)] right = self.get_right_(tokens, position) left = self.get_left_(tokens, position) tokens.insert(position - 1, [self.__TYPE_STATEMENT, left, ope, right]) position = self.find_position(tokens, ['*', '/', '%'], 0, reverse=1) position = 0 position = self.find_position(tokens, ['+', '-'], position) while position >= 0: ope = [self.__TYPE_OPERATOR, tokens.pop(position)] right = self.get_right_(tokens, position) ope_list = ['!_in_', '_in_', '+:=', '-:=', '*:=', '/:=', '%:=', ':=', '+=', '-=', '*=', '/=', '%=', '<=', '>=', '==', '!=', '&&', '||', ',=', '++', '--', ',', '=', '!', '+', '-', '/', '*', '%', '&'] if position == 0 or tokens[position - 1] in ope_list: left = [self.__TYPE_INT, 0] tokens.insert(position, [self.__TYPE_STATEMENT, left, ope, right]) else: left = self.get_left_(tokens, position) tokens.insert(position - 1, [self.__TYPE_STATEMENT, left, ope, right]) position = self.find_position(tokens, ['+', '-'], position) for position in self.iter_position(tokens, ['&']): type_ = tokens[position + 1][0] var_ = tokens[position + 1][1] if type_ == self.__TYPE_ARRAY: tokens.insert(position, [self.__TYPE_ARRAY_POINTER, var_]) elif type_ == self.__TYPE_VARIABLE: tokens.insert(position, [self.__TYPE_VARIABLE_POINTER, var_]) elif type_ == self.__TYPE_TOKEN: tokens.insert(position, [self.__TYPE_VARIABLE_POINTER, [var_, None]]) else: logging.debug( 'syntax error in function "{0}": ' 'illegal argument "{1}"'.format(self.name, tokens)) position = self.find_position(tokens, ['==', '!=', '>=', '<=', '>', '<', '_in_', '!_in_'], 0, reverse=1) while position >= 0: ope = [self.__TYPE_OPERATOR, tokens.pop(position)] right = self.get_right_(tokens, position) left = self.get_left_(tokens, position) tokens.insert(position - 1, [self.__TYPE_CONDITION, [left, ope, right]]) position = self.find_position(tokens, ['==', '!=', '>=', '<=', '>', '<', '_in_', '!_in_'], 0, reverse=1) position = self.find_position(tokens, ['&&'], 0, reverse=1) while position >= 0: ope = [self.__TYPE_OPERATOR, tokens.pop(position)] right = self.get_right_(tokens, position) left = self.get_left_(tokens, position) tokens.insert(position - 1, [self.__TYPE_CONDITION, [left, ope, right]]) position = self.find_position(tokens, ['&&'], 0, reverse=1) position = self.find_position(tokens, ['||'], 0, reverse=1) while position >= 0: ope = [self.__TYPE_OPERATOR, tokens.pop(position)] right = self.get_right_(tokens, position) left = self.get_left_(tokens, position) tokens.insert(position - 1, [self.__TYPE_CONDITION, [left, ope, right]]) position = self.find_position(tokens, ['||'], 0, reverse=1) position = self.find_position(tokens, ['=', ':='], 0, reverse=1) while position >= 0: ope = [self.__TYPE_OPERATOR, tokens.pop(position)] right = self.get_right_(tokens, position) left = self.get_left_(tokens, position) tokens.insert(position - 1, [self.__TYPE_SUBSTITUTION, [left, ope, right]]) position = self.find_position(tokens, ['=', ':='], 0, reverse=1) position = self.find_position(tokens, ['+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:=', ',='], 0, reverse=1) while position >= 0: ope = [self.__TYPE_OPERATOR, tokens.pop(position)] right = self.get_right_(tokens, position) left = self.get_left_(tokens, position) tokens.insert(position - 1, [self.__TYPE_SUBSTITUTION, [left, ope, right]]) position = self.find_position(tokens, ['+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:=', ',='], 0, reverse=1) temp = [] position = self.find_position(tokens, [','], 0, reverse=0) while position >= 0: tokens.pop(position) temp.append(self.get_right_(tokens, position)) if position > 0: temp.insert(-1, self.get_left_(tokens, position)) position = self.find_position(tokens, [','], 0, reverse=0) if temp: tokens.insert(position, [self.__TYPE_NEW_ARRAY, temp]) for i in range(len(tokens)): if isinstance(tokens[i], (str, unicode)): token = tokens.pop(i) tokens.insert(i, self.parse_token(token)) return tokens[0] ## FIXME def parse_(self, line): ## FIXME statement = AyaStatement(line) return self.new_parse(statement.tokens) def parse_statement(self, statement_tokens): n_tokens = len(statement_tokens) statement = [] if n_tokens == 1: statement = [self.__TYPE_STATEMENT, self.parse_token(statement_tokens[0])] elif statement_tokens[0] == '+': statement = self.parse_statement(statement_tokens[1:]) elif statement_tokens[0] == '-': tokens = ['0'] tokens.extend(statement_tokens) statement = self.parse_statement(tokens) else: ope_index = None for ope in ['+', '-']: if ope in statement_tokens: new_index = statement_tokens.index(ope) if ope_index is None or new_index < ope_index: ope_index = new_index if ope_index is None: statement_tokens.reverse() try: for ope in ['*', '/', '%']: if ope in statement_tokens: new_index = statement_tokens.index(ope) if ope_index is None or new_index < ope_index: ope_index = new_index if ope_index is not None: ope_index = -1 - ope_index finally: statement_tokens.reverse() if ope_index in [None, -1, 0, n_tokens - 1]: return None else: ope = [self.__TYPE_OPERATOR, statement_tokens[ope_index]] if len(statement_tokens[:ope_index]) == 1: if statement_tokens[0].startswith('('): tokens = AyaStatement(statement_tokens[0][1:-1]).tokens left = self.parse_statement(tokens) else: left = self.parse_token( statement_tokens[:ope_index][0]) else: left = self.parse_statement(statement_tokens[:ope_index]) if len(statement_tokens[ope_index + 1:]) == 1: if statement_tokens[-1].startswith('('): tokens = AyaStatement( statement_tokens[ope_index + 1][1:-1]).tokens right = self.parse_statement(tokens) else: right = self.parse_token( statement_tokens[ope_index + 1:][0]) else: right = self.parse_statement( statement_tokens[ope_index + 1:]) statement = [self.__TYPE_STATEMENT, left, ope, right] return statement def parse_condition(self, condition_tokens): n_tokens = len(condition_tokens) condition = None ope_index = None condition_tokens.reverse() try: for ope in ['&&', '||']: if ope in condition_tokens: new_index = condition_tokens.index(ope) if ope_index is None or new_index < ope_index: ope_index = new_index if ope_index is not None: ope_index = -1 - ope_index finally: condition_tokens.reverse() if ope_index is None: for ope in ['==', '!=', '>', '<', '>=', '<=', '_in_', '!_in_']: if ope in condition_tokens: new_index = condition_tokens.index(ope) if ope_index is None or new_index < ope_index: ope_index = new_index if ope_index in [None, -1, 0, n_tokens - 1]: logging.debug( 'syntax error in function "{0}": ' 'illegal condition "{1}"'.format(self.name, ' '.join(condition_tokens))) return None ope = [self.__TYPE_OPERATOR, condition_tokens[ope_index]] if len(condition_tokens[:ope_index]) == 1: left = self.parse_statement([condition_tokens[:ope_index][0]]) else: left = self.parse_statement(condition_tokens[:ope_index]) if len(condition_tokens[ope_index + 1:]) == 1: right = self.parse_statement([condition_tokens[ope_index + 1:][0]]) else: right = self.parse_statement(condition_tokens[ope_index + 1:]) condition = [self.__TYPE_CONDITION, [left, ope, right]] else: ope = [self.__TYPE_OPERATOR, condition_tokens[ope_index]] left = self.parse_condition(condition_tokens[:ope_index]) right = self.parse_condition(condition_tokens[ope_index + 1:]) if left is not None and right is not None: condition = [self.__TYPE_CONDITION, [left, ope, right]] return condition def parse_argument(self, args): ## FIXME argument = AyaArgument(args) arguments = [] while argument.has_more_tokens(): token = argument.next_token() if token.startswith('&'): result = self.parse_token(token[1:]) if result[0] == self.__TYPE_ARRAY: arguments.append([self.__TYPE_ARRAY_POINTER, result[1]]) elif result[0] == self.__TYPE_VARIABLE: arguments.append([self.__TYPE_VARIABLE_POINTER, result[1]]) elif result[0] == self.__TYPE_TOKEN: arguments.append([self.__TYPE_VARIABLE_POINTER, [result[1], None]]) else: logging.debug( 'syntax error in function "{0}": ' 'illegal argument "{1}"'.format(self.name, token)) elif token.startswith('('): if not token.endswith(')'): logging.debug( 'syntax error in function "{0}": ' 'unbalanced "(" in the string({1})'.format(self.name, token)) return None else: statement = AyaStatement(token[1:-1]) arguments.append(self.parse_statement(statement.tokens)) else: arguments.append(self.parse_statement([token])) return arguments def parse_token(self, token): result = [] if self.__re_f.match(token): result = [self.__TYPE_FLOAT, token] elif self.__re_d.match(token): result = [self.__TYPE_INT, token] elif token.startswith('"'): text = token[1:] if text.endswith('"'): text = text[:-1] if text.count('"') > 0: logging.debug( 'syntax error in function "{0}": ' '\'"\' in string "{1}"'.format(self.name, text)) if '%' not in text: result = [self.__TYPE_STRING_LITERAL, text] else: result = [self.__TYPE_STRING, text] elif token.startswith("'"): text = token[1:] if text.endswith("'"): text = text[:-1] if text.count("'") > 0: logging.debug( 'syntax error in function "{0}": ' "\"'\" in string \"{1}\"".format(self.name, text)) result = [self.__TYPE_STRING_LITERAL, text] else: pos_parenthesis_open = token.find('(') pos_block_open = token.find('[') if pos_parenthesis_open == 0 and find_not_quoted(token, ',') != -1: ## FIXME: Array if not token.endswith(')'): logging.debug( 'syntax error: unbalnced "(" in "{0}"'.format(token)) else: result = [self.__TYPE_NEW_ARRAY, self.parse_argument(token[1:-1])] ## FIXME elif pos_parenthesis_open != -1 and \ (pos_block_open == -1 or \ pos_parenthesis_open < pos_block_open): # function if not token.endswith(')'): logging.debug( 'syntax error: unbalnced "(" in "{0}"'.format(token)) else: func_name = token[:pos_parenthesis_open] arguments = self.parse_argument( token[pos_parenthesis_open + 1:-1]) for char in self.__SPECIAL_CHARS: if char in func_name: logging.debug( 'illegal character "{0}" in ' 'the name of function "{1}"'.format(char, token)) break else: if self.dic.aya.get_system_functions().exists( func_name): if func_name == 'LOGGING': result = [self.__TYPE_SYSTEM_FUNCTION, [func_name, arguments, token[pos_parenthesis_open + 1:-1]]] else: result = [self.__TYPE_SYSTEM_FUNCTION, [func_name, arguments]] else: result = [self.__TYPE_FUNCTION, [func_name, arguments]] elif pos_block_open != -1: # array if not token.endswith(']'): logging.debug( 'syntax error: unbalnced "[" in "{0}"'.format(token)) else: array_name = token[:pos_block_open] index = self.parse_token(token[pos_block_open + 1:-1]) for char in self.__SPECIAL_CHARS: if char in array_name: logging.debug( 'illegal character "{0}" in ' 'the name of array "{1}"'.format(char, token)) break else: result = [self.__TYPE_ARRAY, [array_name, index]] else: # variable or function for char in self.__SPECIAL_CHARS: if char in token: logging.debug( 'syntax error in function "{0}": ' 'illegal character "{1}" in the name of ' 'function/variable "{2}"'.format(self.name, char, token)) break else: result = [self.__TYPE_TOKEN, token] return result def call(self, argv=None): namespace = AyaNamespace(self.dic.aya) _argv = [] if not argv: namespace.put('_argc', 0) else: namespace.put('_argc', len(argv)) for i in range(len(argv)): if isinstance(argv[i], dict): _argv.append(argv[i]['value']) else: _argv.append(argv[i]) namespace.put('_argv', _argv) self.status = self.__CODE_NONE result = self.evaluate(namespace, self.lines, -1, 0) ## FIXME if argv: for i in range(len(argv)): if isinstance(argv[i], dict): value = _argv[i] name = argv[i]['name'] namespace = argv[i]['namespace'] index = argv[i]['index'] namespace.put(name, value, index) return result def evaluate(self, namespace, lines, index_to_return, is_inner_block, is_block=1, connect=0): ## FIXME result = [] alternatives = [] for line in lines: if not line: continue if line[0] in [self.__TYPE_DECISION, self.__TYPE_RETURN, self.__TYPE_BREAK, self.__TYPE_CONTINUE] or \ self.status in [self.__CODE_RETURN, self.__CODE_BREAK, self.__CODE_CONTINUE]: if alternatives: if is_inner_block: if index_to_return < 0: result.append(random.choice(alternatives)) elif index_to_return <= len(alternatives) - 1: result.append(alternatives[index_to_return]) else: # out of range result.append('') else: result.append(alternatives) alternatives = [] if line[0] == self.__TYPE_RETURN or \ self.status == self.__CODE_RETURN: self.status = self.__CODE_RETURN break elif line[0] == self.__TYPE_BREAK or \ self.status == self.__CODE_BREAK: self.status = self.__CODE_BREAK break elif line[0] == self.__TYPE_CONTINUE or \ self.status == self.__CODE_CONTINUE: self.status = self.__CODE_CONTINUE break elif line[0] == self.__TYPE_BLOCK: inner_func = line[1] local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_func = self.evaluate(local_namespace, inner_func, -1, 1) ## FIXME if result_of_inner_func is not None: alternatives.append(result_of_inner_func) elif line[0] == self.__TYPE_SUBSTITUTION: left, ope, right = line[1] ope = ope[1] if ope in [':=', '+:=', '-:=', '*:=', '/:=', '%:=']: type_float = 1 else: type_float = 0 right_result = self.evaluate(namespace, [right], -1 , 1, 0, 1) ## FIXME if ope not in ['=', ':=']: left_result = self.evaluate_token(namespace, left) right_result = self.operation(left_result, ope[0], right_result, type_float) ope = ope[1:] result_of_substitution = self.substitute(namespace, left, ope, right_result) if connect: ## FIXME alternatives.append(result_of_substitution) elif line[0] == self.__TYPE_INC or \ line[0] == self.__TYPE_DEC: # ++/-- if line[0] == self.__TYPE_INC: ope = '++' elif line[0] == self.__TYPE_DEC: ope = '--' else: return None # should not reach here var_name = line[1] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() value = self.evaluate(namespace, [[self.__TYPE_TOKEN, var_name]], -1, 1, 0) ## FIXME index = None if isinstance(value, (int, float)): if ope == '++': target_namespace.put(var_name, int(value) + 1, index) elif ope == '--': target_namespace.put(var_name, int(value) - 1, index) else: return None # should not reach here else: logging.debug( 'illegal increment/decrement:' 'type of variable {0} is not number'.format(var_name)) elif line[0] == self.__TYPE_IF: inner_blocks = line[1] n_blocks = len(inner_blocks) for j in range(n_blocks): entry = inner_blocks[j] condition = entry[0] inner_block = entry[1] if self.evaluate(namespace, [condition], -1, 1, 0): ## FIXME local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_block = self.evaluate(local_namespace, inner_block, -1, 1) ## FIXME if result_of_inner_block is not None: alternatives.append(result_of_inner_block) break elif line[0] == self.__TYPE_WHILE: condition = line[1][0] inner_block = line[1][1] assert condition[0] == self.__TYPE_CONDITION while self.evaluate_condition(namespace, condition): local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_block = self.evaluate(local_namespace, inner_block, -1, 1) ## FIXME if result_of_inner_block is not None: alternatives.append(result_of_inner_block) if self.status == self.__CODE_RETURN: break if self.status == self.__CODE_BREAK: self.status = self.__CODE_NONE break if self.status == self.__CODE_CONTINUE: self.status = self.__CODE_NONE elif line[0] == self.__TYPE_FOR: init = line[1][0][0] condition = line[1][0][1] reset = line[1][0][2] inner_block = line[1][1] self.evaluate(namespace, init, -1, 1, 0) ## FIXME assert condition[0] == self.__TYPE_CONDITION while self.evaluate_condition(namespace, condition): local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_block = self.evaluate(local_namespace, inner_block, -1, 1) ## FIXME if result_of_inner_block is not None: alternatives.append(result_of_inner_block) if self.status == self.__CODE_RETURN: break if self.status == self.__CODE_BREAK: self.status = self.__CODE_NONE break if self.status == self.__CODE_CONTINUE: self.status = self.__CODE_NONE self.evaluate(namespace, reset, -1, 1, 0) ## FIXME elif line[0] == self.__TYPE_SWITCH: index = self.evaluate_token(namespace, line[1][0]) inner_block = line[1][1] try: index = int(index) except ValueError: index = 0 local_namespace = AyaNamespace(self.dic.aya, namespace) result_of_inner_block = self.evaluate(local_namespace, inner_block, index, 1) ## FIXME if result_of_inner_block is not None: alternatives.append(result_of_inner_block) elif line[0] == self.__TYPE_CASE: left = self.evaluate_token(namespace, line[1][0]) inner_blocks = line[1][1] n_blocks = len(inner_blocks) default_result = None for j in range(n_blocks): entry = inner_blocks[j] inner_block = entry[1] local_namespace = AyaNamespace(self.dic.aya, namespace) if entry[0] is not None: value_min, value_max = entry[0] value_min = self.evaluate_statement(namespace, value_min, 1) value_max = self.evaluate_statement(namespace, value_max, 1) if value_min <= left <= value_max: result_of_inner_block = self.evaluate( local_namespace, inner_block, -1, 1) ## FIXME if result_of_inner_block is not None: alternatives.append(result_of_inner_block) break else: default_result = self.evaluate(local_namespace, inner_block, -1, 1) ## FIXME else: if default_result: alternatives.append(default_result) elif line[0] == self.__TYPE_STATEMENT: result_of_func = self.evaluate_statement(namespace, line, 0) if result_of_func is not None: alternatives.append(result_of_func) elif line[0] == self.__TYPE_CONDITION: condition = line if condition is None or \ self.evaluate_condition(namespace, condition): alternatives.append(1) else: alternatives.append(0) elif line[0] == self.__TYPE_FORMULA: result_of_formula = self.evaluate(namespace, [line[1]], -1, 1, 0, connect=connect) ## FIXME if result_of_formula is not None: alternatives.append(result_of_formula) elif line[0] == self.__TYPE_NEW_ARRAY: temp = [] for item in line[1]: member_of_array = self.evaluate(namespace, [item], -1, 1, 0, 1) ## FIXME temp.append(member_of_array) alternatives.append(temp) elif line[0] == self.__TYPE_ARRAY: system_functions = self.dic.aya.get_system_functions() if isinstance(line[1][0], list) or \ system_functions.exists(line[1][0]): if isinstance(line[1][0], list): ## FIXME array = self.evaluate(namespace, [line[1][0]], -1, 1, 0) ## FIXME else: array = self.evaluate(namespace, [[self.__TYPE_SYSTEM_FUNCTION, [line[1][0], []]]], -1, 1, 0) if isinstance(array, (str, unicode)): temp = self.evaluate(namespace, [line[1][1]], -1, 1, 0) ## FIXME if isinstance(temp, list): assert len(temp) == 2 index, delimiter = temp else: index = temp delimiter = ',' assert isinstance(index, int) assert isinstance(delimiter, (str, unicode)) result_of_array = array.split(delimiter)[index] alternatives.append(result_of_array) elif isinstance(array, list): index = self.evaluate(namespace, [line[1][1]], -1, 1, 0) ## FIXME assert isinstance(index, int) result_of_array = array[index] alternatives.append(result_of_array) else: logging.debug( 'Oops: {0} {1}'.format(repr(array), line[1][1])) else: var_name = line[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() temp = self.evaluate(namespace, [line[1][1]], -1, 1, 0) ## FIXME if isinstance(temp, list) and len(temp) > 1: assert len(temp) == 2 index, delimiter = temp assert isinstance(delimiter, (str, unicode)) else: index = temp delimiter = None ##assert isinstance(index, int) if isinstance(index, int) and \ target_namespace.exists(var_name): if delimiter is not None: array = target_namespace.get(var_name) temp = array.split(delimiter) if len(temp) > index: result_of_array = array.split(delimiter)[index] else: result_of_array = '' else: result_of_array = target_namespace.get(var_name, index) alternatives.append(result_of_array) else: result_of_array = '' alternatives.append(result_of_array) elif line[0] in [self.__TYPE_INT, self.__TYPE_TOKEN, self.__TYPE_SYSTEM_FUNCTION, self.__TYPE_STRING_LITERAL, self.__TYPE_FUNCTION, self.__TYPE_STRING, self.__TYPE_ARRAY_POINTER, self.__TYPE_VARIABLE_POINTER]: result_of_eval = self.evaluate_token(namespace, line) if result_of_eval is not None: alternatives.append(result_of_eval) elif line[0] == self.__TYPE_FOREACH: var_name, temp = line[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() array = target_namespace.get_array(var_name) for item in array: if temp.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() target_namespace.put(temp, item) result_of_block = self.evaluate(namespace, line[1][1], -1, 1) ## FIXME if self.status == self.__CODE_RETURN: break if self.status == self.__CODE_BREAK: self.status = self.__CODE_NONE break if self.status == self.__CODE_CONTINUE: self.status = self.__CODE_NONE else: ## FIXME result_of_eval = self.evaluate_token(namespace, line) if result_of_eval is not None: alternatives.append(result_of_eval) if not is_inner_block: if self.sequential is not None: list_ = [] for alt in result: list_.append(len(alt)) if self.sequential[0] != list_: self.sequential[0] = list_ self.sequential[1] = [0] * len(result) else: for index in range(len(result)): current = self.sequential[1][index] if current < len(result[index]) - 1: self.sequential[1][index] = current + 1 break else: self.sequential[1][index] = 0 if self.nonoverlap is not None: list_ = [] for alt in result: list_.append(len(alt)) if self.nonoverlap[0] != list_: self.nonoverlap[0] = list_ self.nonoverlap[2] = [] if not self.nonoverlap[2]: self.nonoverlap[2].append([0] * len(result)) while 1: new = [] new.extend(self.nonoverlap[2][-1]) for index in range(len(result)): if new[index] < len(result[index]) - 1: new[index] += 1 self.nonoverlap[2].append(new) break else: new[index] = 0 else: break next = random.choice(range(len(self.nonoverlap[2]))) self.nonoverlap[1] = self.nonoverlap[2][next] del self.nonoverlap[2][next] for index in range(len(result)): if self.sequential is not None: result[index] = result[index][self.sequential[1][index]] elif self.nonoverlap is not None: result[index] = result[index][self.nonoverlap[1][index]] else: result[index] = random.choice(result[index]) if not result: if not is_block and alternatives: ## FIXME return alternatives[-1] ## FIXME return None elif len(result) == 1: return result[0] else: return ''.join([unicode(s) for s in result]) def substitute(self, namespace, left, ope, right): if left[0] is not self.__TYPE_ARRAY: var_name = left[1] else: var_name = left[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() if left[0] is not self.__TYPE_ARRAY: target_namespace.put(var_name, right) else: index = self.evaluate(namespace, [left[1][1]], -1, 1, 0) try: index = int(index) except ValueError: logging.debug('Could not convert {0} to an integer'.format(index)) else: if ope == '=': elem = right elif ope == ':=': if isinstance(right, int): elem = float(right) else: elem = right else: return None # should not reach here target_namespace.put(var_name, elem, index) return right def evaluate_token(self, namespace, token): result = '' # default if token[0] == self.__TYPE_TOKEN: if self.__re_b.match(token[1]): pos = self.__re_d.search(token[1]).start() result = int(token[1][pos:], 2) elif self.__re_x.match(token[1]): result = int(token[1], 16) else: func = self.dic.get_function(token[1]) system_functions = self.dic.aya.get_system_functions() if func: result = func.call() elif system_functions.exists(token[1]): result = system_functions.call(namespace, token[1], []) else: if token[1].startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() if target_namespace.exists(token[1]): result = target_namespace.get(token[1]) elif token[0] == self.__TYPE_STRING_LITERAL: result = token[1] elif token[0] == self.__TYPE_STRING: result = self.evaluate_string(namespace, token[1]) elif token[0] == self.__TYPE_INT: result = int(token[1]) elif token[0] == self.__TYPE_FLOAT: result = float(token[1]) elif token[0] == self.__TYPE_SYSTEM_FUNCTION: system_functions = self.dic.aya.get_system_functions() func_name = token[1][0] ##assert system_functions.exists(func_name) ##raise Exception(''.join(('function ', func_name, ' not found.'))) arguments = self.evaluate(namespace, token[1][1], -1, 1, 0, 1) ## FIXME if not isinstance(arguments, list): ## FIXME arguments = [arguments] if func_name == 'LOGGING': arguments.insert(0, token[1][2]) arguments.insert(0, self.name) arguments.insert(0, self.dic.aya.logfile) result = system_functions.call(namespace, func_name, arguments) else: result = system_functions.call(namespace, func_name, arguments) elif token[0] == self.__TYPE_FUNCTION: func_name = token[1][0] func = self.dic.get_function(func_name) ##assert func is not None: ##raise Exception(''.join(('function ', func_name, ' not found.'))) arguments = self.evaluate_argument(namespace, func_name, token[1][1], 0) result = func.call(arguments) elif token[0] == self.__TYPE_ARRAY: result = self.evaluate(namespace, [token], -1, 1, 0) ## FIXME elif token[0] == self.__TYPE_NEW_ARRAY: result = self.evaluate(namespace, [token], -1, 1, 0) ## FIXME elif token[0] == self.__TYPE_VARIABLE: var_name = token[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() if target_namespace.exists(var_name): result = target_namespace.get(var_name) elif token[0] == self.__TYPE_ARRAY_POINTER: var_name = token[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() index = self.evaluate(namespace, [token[1][1]], -1, 1, 0) try: index = int(index) except: logging.debug( 'index of array has to be integer: {0}[{1}]'.format(var_name, token[1][1])) else: value = target_namespace.get(var_name, index) result = {'name': var_name, 'index': index, 'namespace': target_namespace, 'value': value} elif token[0] == self.__TYPE_VARIABLE_POINTER: var_name = token[1][0] if var_name.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() value = target_namespace.get(var_name) result = {'name': var_name, 'index': None, 'namespace': target_namespace, 'value': value} else: logging.debug('error in evaluate_token: {0}'.format(token)) return result def evaluate_condition(self, namespace, condition): result = 0 if condition[1] is None: return 1 left = condition[1][0] ope = condition[1][1] right = condition[1][2] assert ope[0] == self.__TYPE_OPERATOR if left is None: # '!' left_result = 1 else: left_result = self.evaluate(namespace, [left], -1, 1, 0) ## FIXME right_result = self.evaluate(namespace, [right], -1, 1, 0) ## FIXME if ope[1] == '==': result = (left_result == right_result) elif ope[1] == '!=': result = (left_result != right_result) elif ope[1] == '_in_': if isinstance(right_result, (unicode, str)) and isinstance(left_result, (unicode, str)): if left_result in right_result: result = 1 else: result = 0 else: result = 0 elif ope[1] == '!_in_': if isinstance(right_result, (unicode, str)) and isinstance(left_result, (unicode, str)): if left_result not in right_result: result = 1 else: result = 0 else: result = 0 elif ope[1] == '<': result = left_result < right_result elif ope[1] == '>': result = left_result > right_result elif ope[1] == '<=': result = left_result <= right_result elif ope[1] == '>=': result = left_result >= right_result elif ope[1] == '||': result = left_result or right_result elif ope[1] == '&&': result = left_result and right_result elif ope[1] == '!': result = not right_result else: pass return result def evaluate_statement(self, namespace, statement, type_float): num = len(statement[1:]) if num == 0: return '' type_ = statement[0] token = statement[1] if type_ == self.__TYPE_STATEMENT: left = self.evaluate_statement(namespace, token, type_float) else: left = self.evaluate_token(namespace, statement) if num == 3: ope = statement[2][1] type_ = statement[3][0] if type_ == self.__TYPE_INT: token = statement[3][1] if type_float: right = float(token) else: right = int(token) elif type_ == self.__TYPE_FLOAT: token = statement[3][1] if type_float: right = float(token) else: right = int(float(token)) elif type_ == self.__TYPE_STATEMENT: right = self.evaluate_statement(namespace, statement[3], type_float) else: right = self.evaluate(namespace, [statement[3]], -1, 1, 0) ## FIXME result = self.operation(left, ope, right, type_float) else: result = left return result def operation(self, left, ope, right, type_float): if not isinstance(left, list): try: if type_float: left = float(left) right = float(right) elif ope != '+' or \ (not isinstance(left, (str, unicode)) and not isinstance(right, (str, unicode))): left = int(left) right = int(right) else: left = unicode(left) right = unicode(right) except: left = unicode(left) right = unicode(right) try: if ope == '+': return left + right elif ope == '-': return left - right elif ope == '*': return left * right elif ope == '/': if right == 0: return 0 else: return left / right elif ope == '%': return left % right elif ope == ',': assert isinstance(left, list) result = [] result.extend(left) if isinstance(right, list): result.extend(right) else: result.append(right) return result except: logging.debug( 'illegal operation: {0}'.format(' '.join((unicode(left), unicode(ope), unicode(right))))) return '' def get_block(self, parent, startpoint): result = [] n_lines = len(parent) inner_nest_level = 0 for i in range(startpoint, n_lines): inner_content = parent[i] if inner_content == '{': if inner_nest_level > 0: result.append(inner_content) inner_nest_level += 1 elif inner_content == '}': inner_nest_level -= 1 if inner_nest_level > 0: result.append(inner_content) else: result.append(inner_content) if inner_nest_level == 0: return i, result return startpoint, result def evaluate_string(self, namespace, line): history = [] # %[n] buf = '' startpoint = 0 system_functions = self.dic.aya.get_system_functions() while startpoint < len(line): pos = line.find('%', startpoint) if pos < 0: buf = ''.join((buf, line[startpoint:])) startpoint = len(line) continue else: buf = ''.join((buf, line[startpoint:pos])) startpoint = pos if pos == len(line) - 1: # XXX break if line[pos + 1] == '(': start_ = pos + 2 nest = 0 current_ = start_ while line[current_:].count(')') > 0: close_ = line.index(')', current_) if close_ == 0: break nest = line[start_:close_].count('(') - line[start_:close_].count(')') if nest > 0: current_ = close_ + 1 else: current_ = close_ break lines_ = self.parse([line[start_:current_]]) result_ = self.evaluate(namespace, lines_, -1, 1, 0, 1) buf = ''.join((buf, unicode(result_))) startpoint = current_ + 1 continue endpoint = len(line) for char in self.__SPECIAL_CHARS: pos = line.find(char, startpoint + 2, endpoint) if 0 < pos < endpoint: endpoint = pos if line[startpoint + 1] == '[': # history if line[endpoint] != ']': logging.debug( 'unbalanced "%[" or illegal index in ' 'the string({0})'.format(line)) buf = '' break index_token = self.parse_token(line[startpoint + 2:endpoint]) index = self.evaluate_token(namespace, index_token) try: index = int(index) except: logging.debug( 'illegal history index in the string({0})'.format(line)) else: if 0 <= index < len(history): buf = ''.join((buf, self.format(history[index]))) startpoint = endpoint + 1 continue replaced = 0 while endpoint > startpoint + 1: token = line[startpoint + 1:endpoint] func = self.dic.get_function(token) is_system_func = system_functions.exists(token) if func is not None or is_system_func: if endpoint < len(line) and \ line[endpoint] == '(': end_of_parenthesis = line.find(')', endpoint + 1) if end_of_parenthesis < 0: logging.debug( 'unbalanced "(" in the string({0})'.format(line)) startpoint = len(line) buf = '' break func_name = token arguments = self.parse_argument( line[endpoint + 1:end_of_parenthesis]) arguments = self.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]) arguments.insert(0, self.name) arguments.insert(0, self.dic.aya.logfile) result_of_func = system_functions.call( namespace, func_name, arguments) else: result_of_func = system_functions.call( namespace, func_name, arguments) else: result_of_func = func.call(arguments) if result_of_func is None: result_of_func = '' history.append(result_of_func) buf = ''.join((buf, self.format(result_of_func))) startpoint = end_of_parenthesis + 1 replaced = 1 break elif func is not None: result_of_func = func.call() history.append(result_of_func) buf = ''.join((buf, self.format(result_of_func))) startpoint = endpoint replaced = 1 break else: result_of_func = system_functions.call( namespace, token, []) if result_of_func is None: result_of_func = '' history.append(result_of_func) buf = ''.join((buf, self.format(result_of_func))) startpoint = endpoint replaced = 1 break else: if token.startswith('_'): target_namespace = namespace else: target_namespace = self.dic.aya.get_global_namespace() if target_namespace.exists(token): have_index = 0 index = None if endpoint < len(line) and line[endpoint] == '[': end_of_block = line.find(']', endpoint + 1) if end_of_block < 0: logging.debug( 'unbalanced "[" or ' 'illegal index in the string({0})'.format(line)) startpoint = len(line) buf = '' break have_index = 1 index_token = self.parse_token( line[endpoint + 1:end_of_block]) index = self.evaluate_token(namespace, index_token) try: index = int(index) except ValueError: have_index = 0 index = None value = target_namespace.get(token, index) if value is not None: content_of_var = value history.append(content_of_var) buf = ''.join((buf, self.format(content_of_var))) if have_index: startpoint = end_of_block + 1 else: startpoint = endpoint replaced = 1 break endpoint -= 1 if not replaced: buf = ''.join((buf, line[startpoint])) startpoint += 1 return buf def format(self, input_num): if isinstance(input_num, float): result = str(round(input_num, 6)) else: result = unicode(input_num) return result def evaluate_argument(self, namespace, name, argument, is_system_func): arguments = [] for i in range(len(argument)): if is_system_func and \ self.dic.aya.get_system_functions().not_to_evaluate(name, i): arguments.append(argument[i][1][1]) else: value = self.evaluate_statement(namespace, argument[i], 1) if type(value) == list: arguments.extend(value) else: arguments.append(value) return arguments def is_substitution(self, line): statement = AyaStatement(line) if statement.countTokens() >= 3: statement.next_token() # left ope = statement.next_token() ope_list = ['=', ':=', '+=', '-=', '*=', '/=', '%=', '+:=', '-:=', '*:=', '/:=', '%:=', ',='] if ope in ope_list: return 1 return 0 def is_inc_or_dec(self, line): if len(line) <= 2: return 0 if line.endswith('++') or line.endswith('--'): return 1 else: return 0 class AyaSystemFunctions(object): def __init__(self, aya): self.aya = aya self.current_charset = None self.fcharset = {} self.charsetlib = None self.saori_statuscode = '' self.saori_header = [] self.saori_value = {} self.saori_protocol = '' self.errno = 0 self.functions = { 'ACOS': [self.ACOS, [None], [1], None], 'ANY': [], 'ARRAYSIZE': [self.ARRAYSIZE, [0], [1], None], 'ASEARCH': [], 'ASEARCHEX': [], 'ASIN': [self.ASIN, [None], [1], None], 'ATAN': [self.ATAN, [None], [1], None], 'BINSTRTOI': [self.BINSTRTOI, [None], [1], None], 'CEIL': [self.CEIL, [None], [1], None], 'CHARSETLIB': [self.CHARSETLIB, [None], [1], None], 'CHR': [self.CHR, [None], [1], None], 'CHRCODE': [self.CHRCODE, [None], [1], None], 'COS': [self.COS, [None], [1], None], 'CUTSPACE': [self.CUTSPACE, [None], [1], None], 'CVINT': [self.CVINT, [0], [1], None], 'CVREAL': [self.CVREAL, [0], [1], None], 'CVSTR': [self.CVSTR, [0], [1], None], 'ERASE': [self.ERASE, [None], [3], None], 'ERASEVAR': [self.ERASEVAR, [None], [1], None], 'EVAL': [self.EVAL, [None], [1], None], 'FATTRIB': [], 'FCHARSET': [self.FCHARSET, [None], [1], None], 'FCLOSE': [self.FCLOSE, [None], [1], None], 'FCOPY': [self.FCOPY, [None], [2], 259], 'FDEL': [self.FDEL, [None], [1], 269], 'FENUM': [self.FENUM, [None], [1, 2], 290], 'FLOOR': [self.FLOOR, [None], [1], None], 'FMOVE': [self.FMOVE, [None], [2], 264], 'FOPEN': [self.FOPEN, [None], [2], 256], 'FREAD': [self.FREAD, [None], [1], None], 'FRENAME': [self.FRENAME, [None], [2], 273], 'FSIZE': [self.FSIZE, [None], [1], 278], 'FWRITE': [self.FWRITE, [None], [2], None], 'FWRITE2': [self.FWRITE2, [None], [2], None], 'GETDELIM': [self.GETDELIM, [0], [1], None], 'GETLASTERROR': [self.GETLASTERROR, [None], [None], None], 'GETMEMINFO': [], 'GETSETTING': [self.GETSETTING, [None], [1], None], 'GETSTRBYTES': [self.GETSTRBYTES, [None], [1, 2], None], 'GETTICKCOUNT': [self.GETTICKCOUNT, [None], [0], None], 'GETTIME': [self.GETTIME, [None], [0], None], 'GETTYPE': [self.GETTYPE, [None], [1], None], 'HEXSTRTOI': [self.HEXSTRTOI, [None], [1], None], 'IARRAY': [self.IARRAY, [None], [None], None], 'INSERT': [self.INSERT, [None], [3], None], 'ISFUNC': [self.ISFUNC, [None], [1], None], 'ISINTSTR': [self.ISINTSTR, [None], [1], None], 'ISREALSTR': [self.ISREALSTR, [None], [1], None], 'ISVAR': [self.ISVAR, [None], [1], None], 'LETTONAME': [self.LETTONAME, [None], [2], None], 'LOADLIB': [self.LOADLIB, [None], [1], 16], 'LOG': [self.LOG, [None], [1], None], 'LOG10': [self.LOG10, [None], [1], None], 'LOGGING': [], 'LSO': [], 'MKDIR': [self.MKDIR, [None], [1], 282], 'POW': [self.POW, [None], [2], None], 'RAND': [self.RAND, [None], [0, 1], None], 'RE_GETLEN': [], 'RE_GETPOS': [], 'RE_GETSTR': [], 'RE_GREP': [], 'RE_MATCH': [], 'RE_REPLACE': [], 'RE_SEARCH': [], 'RE_SPLIT': [self.RE_SPLIT, [None], [2], None], 'REPLACE': [self.REPLACE, [None], [3], None], 'REQUESTLIB': [self.REQUESTLIB, [None], [2], None], 'RMDIR': [self.RMDIR, [None], [1], 286], 'ROUND': [self.ROUND, [None], [1], None], 'SAVEVAR': [self.SAVEVAR, [None], [None], None], 'SETDELIM': [self.SETDELIM, [0], [2], None], 'SETLASTERROR': [self.SETLASTERROR, [None], [1], None], 'SIN': [self.SIN, [None], [1], None], 'SPLIT': [self.SPLIT, [None], [2, 3], None], 'SPLITPATH': [self.SPLITPATH, [None], [1], None ], 'SQRT': [self.SQRT, [None], [1], None], 'STRFORM': [], 'STRLEN': [self.STRLEN, [1], [1], None], 'STRSTR': [self.STRSTR, [3], [3], None], 'SUBSTR': [self.SUBSTR, [None], [3], None], 'TAN': [self.TAN, [None], [1], None], 'TOBINSTR': [self.TOBINSTR, [None], [1], None], 'TOHEXSTR': [self.TOHEXSTR, [None], [1], None], 'TOINT': [self.TOINT, [None], [1], None], 'TOLOWER': [self.TOLOWER, [None], [1], None], 'TOREAL': [self.TOREAL, [None], [1], None], 'TOSTR': [self.TOSTR, [None], [1], None], 'TOUPPER': [self.TOUPPER, [None], [1], None], 'UNLOADLIB': [self.UNLOADLIB, [None], [1], None], #'LOGGING': [self.LOGGING, [None], [4], None] } def exists(self, name): return name in self.functions def call(self, namespace, name, argv): self.errno = 0 if name not in self.functions: return '' elif not self.functions[name]: # not implemented yet logging.warning( 'aya5.py: SYSTEM FUNCTION "{0}" is not implemented yet.'.format(name)) return '' elif self.check_num_args(name, argv): return self.functions[name][0](namespace, argv) else: return '' def not_to_evaluate(self, name, index): if index in self.functions[name][1]: return 1 else: return 0 def check_num_args(self, name, argv): list_num = self.functions[name][2] if list_num == [None]: return 1 else: if len(argv) in list_num: return 1 list_num.sort() if len(argv) < list_num[0]: errno = self.functions[name][3] if errno is not None: self.errno = errno logging.debug( ''.join((str(name), ': called with too few argument(s)'))) return 0 return 1 def ACOS(self, namespace, argv): try: result = math.acos(float(argv[0])) except: return -1 return self.select_math_type(result) def ANY(self, namespace, argv): pass def ARRAYSIZE(self, namespace, argv): return len(argv) ## FIXME def ASEARCH(self, namespace, argv): pass def ASEARCHEX(self, namespace, argv): pass def ASIN(self, namespace, argv): try: result = math.asin(float(argv[0])) except: return -1 return self.select_math_type(result) def ATAN(self, namespace, argv): try: result = math.atan(float(argv[0])) except: return -1 return self.select_math_type(result) def BINSTRTOI(self, namespace, argv): try: return int(str(argv[0]), 2) except: return 0 def CEIL(self, namespace, argv): try: return int(math.ceil(float(argv[0]))) except: return -1 def CHARSETLIB(self, namespace, argv): try: value = int(argv[0]) except: return if value == 0: self.charsetlib = 'Shift_JIS' elif value == 1: self.charsetlib = 'UTF-8' elif value == 127: self.charsetlib = self.aya.charset def CHR(self, namespace, argv): try: return unichr(argv[0]) except: return '' def CHRCODE(self, namespace, argv): line = unicode(argv[0]) if line: return line[0].encode('utf-16', 'ignore') # UCS-2 else: return '' def COS(self, namespace, argv): try: result = math.cos(float(argv[0])) except: return -1 return self.select_math_type(result) def CUTSPACE(self, namespace, argv): return unicode(argv[0]).strip() def CVINT(self, namespace, argv): var = str(argv[0]) target_namespace = self.select_namespace(namespace, var) token = target_namespace.get(var) try: result = int(token) except: result = 0 target_namespace.put(var, result) return None def CVREAL(self, namespace, argv): var = str(argv[0]) target_namespace = self.select_namespace(namespace, var) token = target_namespace.get(var) try: result = float(token) except: result = 0.0 target_namespace.put(var, result) return None def CVSTR(self, namespace, argv): name = str(argv[0]) target_namespace = self.select_namespace(namespace, name) value = unicode(target_namespace.get(name)) target_namespace.put(name, value) return None def ERASE(self, namespace, argv): line = unicode(argv[0]) try: start = int(argv[1]) num = int(argv[2]) except: return '' return ''.join((line[:start], line[start + num:])) def ERASEVAR(self, namespace, argv): var = str(argv[0]) target_namespace = self.select_namespace(namespace, var) target_namespace.remove(var) def EVAL(self, namespace, argv): script = argv[0] func = AyaFunction(self.aya.dic, '', [script], None) result = func.call() return result def FATTRIB(self, namespace, argv): pass def FCHARSET(self, namespace, argv): try: value = int(argv[0]) except: return if value == 0: self.current_charset = 'Shift_JIS' elif value == 1: self.current_charset = 'UTF-8' elif value == 127: self.current_charset = self.aya.charset def FCLOSE(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) norm_path = os.path.normpath(path) if norm_path in self.aya.filelist: self.aya.filelist[norm_path].close() del self.aya.filelist[norm_path] del self.f_charset[norm_path] return None def FCOPY(self, namespace, argv): src = get_normalized_path(str(argv[0]), encode=0) head, tail = os.path.split(src) dst = ''.join((get_normalized_path(str(argv[1]), encode=0), '/', tail)) src_path = os.path.join(self.aya.aya_dir, src) dst_path = os.path.join(self.aya.aya_dir, dst) result = 0 if not os.path.isfile(src_path): self.errno = 260 elif not os.path.isdir(dst_path): self.errno = 261 else: try: shutil.copyfile(src_path, dst_path) except: self.errno = 262 else: result = 1 return result def FDEL(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) result = 0 if not os.path.isfile(path): self.errno = 270 else: try: os.remove(path) except: self.errno = 271 else: result = 1 return result def FENUM(self, namespace, argv): if len(argv) >= 2: separator = str(argv[1]) else: separator = ',' dirname = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, dirname) filelist = [] try: filelist = os.listdir(path) except: self.errno = 291 result = '' for index in range(len(filelist)): path = os.path.join(self.aya.aya_dir, dirname, filelist[index]) if os.path.isdir(path): result = ''.join((result, '\\')) result = ''.join((result, filelist[index])) if index != len(filelist) - 1: result = ''.join((result, separator)) return result def FLOOR(self, namespace, argv): try: return int(math.floor(float(argv[0]))) except: return -1 def FMOVE(self, namespace, argv): src = get_normalized_path(str(argv[0]), encode=0) head, tail = os.path.split(src) dst = ''.join((get_normalized_path(str(argv[1]), encode=0), '/', tail)) src_path = os.path.join(self.aya.aya_dir, src) dst_path = os.path.join(self.aya.aya_dir, dst) result = 0 head, tail = os.path.split(dst_path) if not os.path.isfile(src_path): self.errno = 265 elif not os.path.isdir(head): self.errno = 266 else: try: os.rename(src_path, dst_path) except: self.errno = 267 else: result = 1 return result def FOPEN(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) accessmode = str(argv[1]) result = 0 path = os.path.join(self.aya.aya_dir, filename) norm_path = os.path.normpath(path) if norm_path in self.aya.filelist: result = 2 else: try: self.aya.filelist[norm_path] = open(path, accessmode[0]) except: self.errno = 257 else: if self.current_charset is None: self.current_charset = self.aya.charset self.f_charset[norm_path] = self.current_charset result = 1 return result def FREAD(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) norm_path = os.path.normpath(path) result = -1 if norm_path in self.aya.filelist: f = self.aya.filelist[norm_path] result = unicode( f.readline(), self.fcharset[norm_path], 'ignore') if not result: result = -1 elif result.endswith('\r\n'): result = result[:-2] elif result.endswith('\n'): result = result[:-1] return result def FRENAME(self, namespace, argv): src = get_normalized_path(str(argv[0]), encode=0) dst = get_normalized_path(str(argv[1]), encode=0) src_path = os.path.join(self.aya.aya_dir, src) dst_path = os.path.join(self.aya.aya_dir, dst) result = 0 head, tail = os.path.split(dst_path) if not os.path.exists(src_path): self.errno = 274 elif not os.path.isdir(head): self.errno = 275 else: try: os.rename(src_path, dst_path) except: self.errno = 276 else: result = 1 return result def FSIZE(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) size = -1 if not os.path.exists(path): self.errno = 279 else: try: size = os.path.getsize(path) except: self.errno = 280 return size def FWRITE(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) norm_path = os.path.normpath(path) data = ''.join((str(argv[1]), '\n')) if norm_path in self.aya.filelist: f = self.aya.filelist[norm_path] f.write(data.encode(self.fcharset[norm_path], 'ignore')) return None def FWRITE2(self, namespace, argv): filename = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, filename) norm_path = os.path.normpath(path) data = str(argv[1]) if norm_path in self.aya.filelist: f = self.aya.filelist[norm_path] f.write(data.encode(self.fcharset[norm_path], 'ignore')) return None def GETDELIM(self, namespace, argv): name = str(argv[0]) target_namespace = self.select_namespace(namespace, name) return target_namespace.get_separator(name) def GETLASTERROR(self, namespace, argv): return self.errno def GETMEMINFO(self, namespace, argv): pass def GETSETTING(self, namespace, argv): try: value = int(argv[0]) except: return '' if value == 0: result = '5' elif value == 1: if self.aya.charset == 'Shift_JIS': result = 0 elif self.aya.charset == 'UTF-8': result = 1 else: result = 2 elif value == 2: result = self.aya.aya_dir else: result = '' return result def GETSTRBYTES(self, namespace, argv): line = unicode(argv[0]) if len(argv) > 1: try: value = int(argv[1]) except: value = 0 else: value = 0 if value == 0: result = len(line.encode('Shift_JIS', 'ignore')) elif value == 1: result = len(line.encode('utf-8', 'ignore')) elif value == 2: result = len(line.encode(self.aya.charset, 'ignore')) else: result = -1 return result def GETTICKCOUNT(self, namespace, argv): t = time.localtime(time.time()) past = time.time() - self.aya.get_boot_time() return int(past * 1000.0) def GETTIME(self, namespace, argv): year_, month_, day_, hour_, minute_, second_, wday_, yday_, isdst_ = time.localtime() wday_ = (wday_ + 1) % 7 return [year_, month_, day_, wday_, hour_, minute_, second_] def GETTYPE(self, namespace, argv): if isinstance(argv[0], int): result = 1 elif isinstance(argv[0], float): result = 2 elif isinstance(argv[0], (unicode, str)): result = 3 elif 0: ## FIXME: array result = 4 else: result = 0 return result def HEXSTRTOI(self, namespace, argv): try: return int(str(argv[0]), 16) except: return 0 def IARRAY(self, namespace, argv): return [] # AyaVariable('', new_array=1) ## FIXME def INSERT(self, namespace, argv): line = unicode(argv[0]) try: start = int(argv[1]) except: return '' to_insert = unicode(argv[2]) if start < 0: start = 0 return ''.join((line[:start], to_insert, line[start:])) def ISFUNC(self, namespace, argv): if not isinstance(argv[0], (unicode, str)): return 0 elif self.aya.dic.get_function(argv[0]) is not None: return 1 elif self.aya.get_system_functions().exists(argv[0]): return 2 else: return 0 def ISINTSTR(self, namespace, argv): try: int(str(argv[0])) return 1 except: return 0 def ISREALSTR(self, namespace, argv): try: float(str(argv[0])) return 1 except: return 0 def ISVAR(self, namespace, argv): var = str(argv[0]) if var.startswith('_'): if namespace.exists(var): return 2 else: return 0 else: if self.aya.get_global_namespace().exists(var): return 1 else: return 0 def LETTONAME(self, namespace, argv): var = str(argv[0]) value = argv[1] if not var: return None target_namespace = self.select_namespace(namespace, var) target_namespace.put(var, value) return None def LOADLIB(self, namespace, argv): dll = str(argv[0]) result = 0 if dll: if self.charsetlib is None: self.charsetlib = self.aya.charset self.aya.saori_library.set_charset(self.charsetlib) result = self.aya.saori_library.load(dll, self.aya.aya_dir) if result == 0: self.errno = 17 return result def LOG(self, namespace, argv): try: float(argv[0]) except: return -1 if float(argv[0]) == 0: return 0 result = math.log(float(argv[0])) return self.select_math_type(result) def LOG10(self, namespace, argv): try: float(argv[0]) except: return -1 if float(argv[0]) == 0: return 0 result = math.log10(float(argv[0])) return self.select_math_type(result) def LSO(self, namespace, argv): pass def MKDIR(self, namespace, argv): dirname = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, dirname) result = 0 head, tail = os.path.split(path) if not os.path.isdir(head): self.errno = 283 else: try: os.mkdir(path, 0755) except: self.errno = 284 else: result = 1 return result def POW(self, namespace, argv): try: result = math.pow(float(argv[0]), float(argv[1])) except: return -1 return self.select_math_type(result) def RAND(self, namespace, argv): if not argv: return int(random.randrange(0, 100, 1)) else: try: int(argv[0]) except: return -1 return int(random.randrange(0, int(argv[0]), 1)) def RE_GETLEN(self, namespace, argv): pass def RE_GETPOS(self, namespace, argv): pass def RE_GETSTR(self, namespace, argv): pass def RE_GREP(self, namespace, argv): pass def RE_MATCH(self, namespace, argv): pass def RE_REPLACE(self, namespace, argv): pass def RE_SEARCH(self, namespace, argv): pass def RE_SPLIT(self, namespace, argv): line = unicode(argv[0]) result = line.split(unicode(argv[1])) return result ## FIXME def REPLACE(self, namespace, argv): line = unicode(argv[0]) old = unicode(argv[1]) new = unicode(argv[2]) return line.replace(old, new) def REQUESTLIB(self, namespace, argv): response = self.aya.saori_library.request(argv[0], argv[1]) header = response.splitlines() line = header.pop(0) self.saori_statuscode = '' self.saori_header = [] self.saori_value = {} self.saori_protocol = '' if line: line = line.strip() if ' ' in line: self.saori_protocol, self.saori_statuscode = [x.strip() for x in line.split(' ', 1)] for line in header: if ':' not in line: continue key, value = [x.strip() for x in line.split(':', 1)] if key: self.saori_header.append(key) self.saori_value[key] = value return response def RMDIR(self, namespace, argv): dirname = get_normalized_path(str(argv[0]), encode=0) path = os.path.join(self.aya.aya_dir, dirname) result = 0 if not os.path.isdir(path): self.errno = 287 else: try: os.rmdir(path) except: self.errno = 288 else: result = 1 return result def ROUND(self, namespace, argv): try: value = math.floor(float(argv[0]) + 0.5) except: return -1 return int(value) def SAVEVAR(self, namespace, argv): self.aya.get_global_namespace().save_database() def SETDELIM(self, namespace, argv): name = unicode(argv[0]) separator = unicode(argv[1]) target_namespace = self.select_namespace(namespace, name) target_namespace.set_separator(name, separator) return None def SETLASTERROR(self, namespace, argv): try: value = int(argv[0]) except: return self.errno = value def SIN(self, namespace, argv): try: result = math.sin(float(argv[0])) except: return -1 return self.select_math_type(result) def SPLIT(self, namespace, argv): line = unicode(argv[0]) result = line.split(unicode(argv[1]), int(argv[2])) return result def SPLITPATH(self, namespace, argv): line = unicode(argv[0]) drive, path = os.path.splitdrive(line) dirname, filename = os.path.split(path) basename, ext = os.path.splitext(filename) return [drive, dirname, basename, ext] def SQRT(self, namespace, argv): try: arg = float(argv[0]) except: return -1 if arg < 0.: return -1 else: result = math.sqrt(arg) return self.select_math_type(result) def STRFORM(self, namespace, argv): pass def STRLEN(self, namespace, argv): line = unicode(argv[0]) return len(line) def STRSTR(self, namespace, argv): line = unicode(argv[0]) to_find = unicode(argv[1]) try: start = int(argv[2]) except: return -1 result = line.find(to_find, start) return result def SUBSTR(self, namespace, argv): line = unicode(argv[0]) try: start = int(argv[1]) num = int(argv[2]) except: return '' return line[start:start + num] def TAN(self, namespace, argv): try: result = math.tan(float(argv[0])) except: return -1 return self.select_math_type(result) def TOBINSTR(self, namespace, argv): try: i = int(argv[0]) except: return '' if i < 0: i = abs(i) numsin = '-' else: numsin = '' line = '' while i: mod = i % 2 i /= 2 line = ''.join((str(mod), line)) line = ''.join((numsin, line)) return line def TOHEXSTR(self, namespace, argv): try: return '{0:x}'.format(int(argv[0])) except: return '' def TOINT(self, namespace, argv): token = str(argv[0]) try: value = int(token) except: return 0 else: return value def TOLOWER(self, namespace, argv): return unicode(argv[0]).lower() def TOREAL(self, namespace, argv): token = str(argv[0]) try: value = float(token) except: return 0.0 else: return value def TOSTR(self, namespace, argv): return unicode(argv[0]) def TOUPPER(self, namespace, argv): return unicode(argv[0]).upper() def UNLOADLIB(self, namespace, argv): if str(argv[0]): self.aya.saori_library.unload(str(argv[0])) return None def LOGGING(self, namespace, argv): ## FIXME if argv[0] is None: return None logfile = argv[0] line = ''.join(('> function ', str(argv[1]), ' : ', str(argv[2]))) if argv[3] is not None: line = ''.join((line, ' = ')) if isinstance(argv[3], int) or isinstance(argv[3], float): line = ''.join((line, str(argv[3]))) else: line = ''.join((line, '"', str(argv[3]), '"')) line = ''.join((line, '\n')) logfile.write(line) logfile.write('\n') return None def select_math_type(self, value): if math.floor(value) == value: return int(value) else: return value def select_namespace(self, namespace, name): if name.startswith('_'): return namespace else: return self.aya.get_global_namespace() class AyaNamespace(object): def __init__(self, aya, parent=None): self.aya = aya self.parent = parent self.table = {} def put(self, name, content, index=None): if self.parent is not None and self.parent.exists(name): self.parent.put(name, content, index) elif index is None: if not self.exists(name): self.table[name] = AyaVariable(name) self.table[name].put(content) elif self.exists(name) and index >=0: self.table[name].put(content, index) else: pass # ERROR def get(self, name, index=None): if name in self.table: return self.table[name].get(index) elif self.parent is not None and self.parent.exists(name): return self.parent.get(name, index) else: return None def get_array(self, name): if name in self.table: return self.table[name].get_array() elif self.parent is not None and self.parent.exists(name): return self.parent.get_array(name) else: return [] def get_separator(self, name): if self.parent is not None and self.parent.exists(name): return self.parent.get_separator(name) elif name in self.table: self.table[name].get_separator() else: return '' # ERROR def set_separator(self, name, separator): if self.parent is not None and self.parent.exists(name): self.parent.set_separator(name, separator) elif name in self.table: self.table[name].set_separator(separator) else: pass # ERROR def get_size(self, name): if name in self.table: return self.table[name].get_size() elif self.parent is not None and self.parent.exists(name): return self.parent.get_size(name) else: return 0 def remove(self, name): # only works with local table if name in self.table: del self.table[name] def exists(self, name): result = name in self.table or \ (self.parent is not None and self.parent.exists(name)) return result class AyaGlobalNamespace(AyaNamespace): def load_database(self, aya): try: with open(aya.dbpath) as f: line = f.readline() if not line.startswith('# Format: v1.2'): f.close() return 1 for line in f: comma = line.find(',') if comma >= 0: key = line[:comma] else: continue value = line[comma + 1:].strip() comma = find_not_quoted(value, ',') if comma >= 0: separator = value[comma + 1:].strip() separator = separator[1:-1] value = value[:comma].strip() value = value[1:-1] self.put(key, unicode(value, self.aya.charset)) self.table[key].set_separator(unicode(separator, self.aya.charset)) elif value != 'None': if '.' in value: self.put(key, float(value)) else: self.put(key, int(value)) else: pass except: return 1 return 0 def save_database(self): try: with open(self.aya.dbpath, 'w') as f: f.write('# Format: v1.2\n') for key in self.table.keys(): line = self.table[key].dump(self.aya.charset) if line is not None: f.write(''.join((line, '\n'))) except IOError: logging.debug('aya.py: cannot write database (ignored)') return class AyaStatement(object): __SPECIAL_CHARS = '=+-*/<>|&!:,_' def __init__(self, line): self.n_tokens = 0 self.tokens = [] self.position_of_next_token = 0 self.tokenize(line) def tokenize(self, line): ## FIXME: '[', ']' token_startpoint = 0 block_nest_level = 0 length = len(line) i = 0 while i < length: c = line[i] if c == '(': block_nest_level += 1 self.append_unless_empty(line[token_startpoint:i].strip()) self.tokens.append('(') i += 1 token_startpoint = i elif c == ')': block_nest_level -= 1 self.append_unless_empty(line[token_startpoint:i].strip()) self.tokens.append(')') i += 1 token_startpoint = i elif c == '[': self.append_unless_empty(line[token_startpoint:i].strip()) self.tokens.append('[') i += 1 token_startpoint = i elif c == ']': self.append_unless_empty(line[token_startpoint:i].strip()) self.tokens.append(']') i += 1 token_startpoint = i elif c == '"': position = line.find('"', i + 1) if position < 0: ## FIXME raise SystemExit i = position self.tokens.append(line[token_startpoint:position + 1]) token_startpoint = position + 1 i = position + 1 elif c == "'": position = line.find("'", i + 1) if position < 0: ## FIXME raise SystemExit i = position self.tokens.append(line[token_startpoint:position + 1]) token_startpoint = position + 1 i = position + 1 elif c == ' ' or c == '\t' or c == unicode(' ', 'utf-8'): self.append_unless_empty(line[token_startpoint:i].strip()) i += 1 token_startpoint = i elif c in self.__SPECIAL_CHARS: ope_list = ['!_in_', '_in_', '+:=', '-:=', '*:=', '/:=', '%:=', ':=', '+=', '-=', '*=', '/=', '%=', '<=', '>=', '==', '!=', '&&', '||', ',=', '++', '--', ',', '=', '!', '+', '-', '/', '*', '%', '&'] for ope in ope_list: if line[i:].startswith(ope): ## FIXME self.append_unless_empty(line[token_startpoint:i].strip()) num = len(ope) self.tokens.append(line[i:i + num]) i += num token_startpoint = i break else: i += 1 else: i += 1 self.append_unless_empty(line[token_startpoint:].strip()) self.n_tokens = len(self.tokens) def append_unless_empty(self, token): if token: self.tokens.append(token) def has_more_tokens(self): return (self.position_of_next_token < self.n_tokens) def countTokens(self): return self.n_tokens def next_token(self): if not self.has_more_tokens(): return None result = self.tokens[self.position_of_next_token] self.position_of_next_token += 1 return result class AyaVariable(object): __TYPE_STRING = 0 __TYPE_INT = 1 __TYPE_REAL = 2 __TYPE_ARRAY = 3 __TYPE_NEW_ARRAY = 4 def __init__(self, name, new_array=0): self.name = name self.line = '' self.separator = ',' if new_array: self.type = self.__TYPE_NEW_ARRAY else: self.type = None self.array = [] def get_array(self): return self.array def get_separator(self): return self.separator def set_separator(self, separator): if self.type != self.__TYPE_STRING: return self.separator = separator self.reset() def reset(self): if self.type != self.__TYPE_STRING: return self.position = 0 self.is_empty = 0 self.array = [] while not self.is_empty: separator_position = self.line.find(self.separator, self.position) if separator_position == -1: token = self.line[self.position:] self.is_empty = 1 else: token = self.line[self.position:separator_position] self.position = separator_position + len(self.separator) self.array.append(token) def get_size(self): return len(self.array) def get(self, index=None): if 0 <= index < len(self.array): value = self.array[index] if self.type == self.__TYPE_STRING: result = unicode(value) elif self.type == self.__TYPE_INT: result = int(value) elif self.type == self.__TYPE_REAL: result = float(value) elif self.type == self.__TYPE_ARRAY: result = value elif self.type == self.__TYPE_NEW_ARRAY: result = value ## FIXME else: result = None # should not reach here elif index is None: if self.type == self.__TYPE_STRING: result = unicode(self.line) elif self.type == self.__TYPE_INT: result = int(self.line) elif self.type == self.__TYPE_REAL: result = float(self.line) elif self.type == self.__TYPE_NEW_ARRAY: result = self.array ## FIXME(?) else: result = '' else: result = '' return result def put(self, value, index=None): if index is None: self.line = unicode(value) if isinstance(value, (unicode, str)): self.type = self.__TYPE_STRING elif isinstance(value, int): self.type = self.__TYPE_INT elif isinstance(value, float): self.type = self.__TYPE_REAL elif isinstance(value, list): self.type = self.__TYPE_NEW_ARRAY # CHECK self.array = value self.reset() elif index < 0: pass else: if self.type == self.__TYPE_STRING: self.line = '' for i in range(len(self.array)): if i == index: self.line = ''.join((self.line, unicode(value))) else: self.line = ''.join((self.line, self.array[i])) if i != len(self.array)-1: self.line = ''.join((self.line, self.separator)) if index >= len(self.array): for i in range(len(self.array), index + 1): if i == index: self.line = ''.join((self.line, self.separator, unicode(value))) else: self.line = ''.join((self.line, self.separator, '')) self.reset() elif self.type == self.__TYPE_ARRAY: if 0 <= index < len(self.array): self.array[index] = value elif self.type == self.__TYPE_NEW_ARRAY: if 0 <= index < len(self.array): self.array[index] = value elif index > 0: ## FIXME logging.info('!!! WARNING !!!') logging.info('NOT YET IMPLEMNETED') else: pass # ERROR else: pass # ERROR def dump(self, charset): line = None if self.type == self.__TYPE_STRING: line = u'{0}, "{1}", "{2}"'.format(self.name, self.line, self.separator) line = line.encode(charset, 'ignore') elif self.type == self.__TYPE_NEW_ARRAY: pass ## FIXME elif self.type != self.__TYPE_ARRAY: line = u'{0}, {1}'.format(self.name, self.line) line = line.encode(charset, 'ignore') else: pass return line class AyaArgument(object): def __init__(self, line): self.line = line.strip() self.length = len(self.line) self.current_position = 0 def has_more_tokens(self): return (self.current_position != -1 and \ self.current_position < self.length) def next_token(self): if not self.has_more_tokens(): return None startpoint = self.current_position self.current_position = self.position_of_next_token() if self.current_position == -1: token = self.line[startpoint:] else: token = self.line[startpoint:self.current_position-1] return token.strip() def position_of_next_token(self): locked = 1 position = self.current_position parenthesis_nest_level = 0 while position < self.length: c = self.line[position] if c == '"': if not locked: return position while position < self.length-1: position += 1 if self.line[position] == '"': break elif c == '(': parenthesis_nest_level += 1 elif c == ')': parenthesis_nest_level -= 1 elif c == ',': if parenthesis_nest_level == 0: locked = 0 else: if not locked: return position position += 1 return -1 class AyaSaoriLibrary(object): def __init__(self, saori, top_dir): self.saori_list = {} self.saori = saori self.current_charset = None self.charset = {} def set_charset(self, charset): assert charset in ['Shift_JIS', 'UTF-8'] self.current_charset = charset def load(self, name, top_dir): result = 0 if self.saori and name not in self.saori_list: module = self.saori.request(name) if module: self.saori_list[name] = module if name in self.saori_list: result = self.saori_list[name].load(top_dir) self.charset[name] = self.current_charset return result def unload(self, name=None): if name: if name in self.saori_list: self.saori_list[name].unload() del self.saori_list[name] del self.charset[name] else: for key in self.saori_list.keys(): self.saori_list[key].unload() return None def request(self, name, req): result = '' # FIXME if name and name in self.saori_list: result = self.saori_list[name].request( req.encode(self.current_charset)) return result def test(top_dir, function, argv, dll='aya.dll'): logger = logging.getLogger() logger.setLevel(logging.DEBUG) # XXX aya = Shiori(dll) print 'DIR:', top_dir aya.load(top_dir) # print dir(aya) # print aya.dic.functions result = aya.request('GET SHIORI/3.0\r\n' \ 'ID: {0}\r\n' \ 'Sender: AYA\r\n' \ 'SecurityLevel: local\r\n\r\n'.format(function)) #result = aya.dic.get_function(function).call(argv) print unicode(result, aya.charset) if __name__ == '__main__': USAGE= 'Usage(1): aya.py []\n' \ 'Usage(2): aya.py encrypt ' if len(sys.argv) < 3: print USAGE elif sys.argv[1] == 'encrypt': if len(sys.argv) != 4: print USAGE else: path = os.path.join(os.curdir, sys.argv[2]) with open(path) as inputf: path = os.path.join(os.curdir, sys.argv[3]) with open(path, 'w') as outputf: while 1: c = inputf.read(1) if c == '': break outputf.write(encrypt_char(c)) else: test(sys.argv[1], sys.argv[3], sys.argv[4:], sys.argv[2]) ninix-aya-4.3.9/lib/ninix/dll/bln.py000066400000000000000000000646201172114553600172340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # bln.py - a easyballoon compatible Saori module for ninix # Copyright (C) 2002-2012 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 import os import random import math import time import sys import logging import gtk import glib import pango import cairo import ninix.pix import ninix.script from ninix.dll import SAORI class Saori(SAORI): def __init__(self): SAORI.__init__(self) self.blns = {} self.__sakura = None def need_ghost_backdoor(self, sakura): self.__sakura = sakura def check_import(self): return 1 if self.__sakura else 0 def setup(self): self.blns = self.read_bln_txt(self.dir) if self.blns: self.__sakura.attach_observer(self) return 1 else: return 0 def read_bln_txt(self, dir): blns = {} try: with open(os.path.join(dir, 'bln.txt'), 'r') as f: data = {} name = '' for line in f: line = line.strip() if not line: continue if line.startswith('//'): continue if '[' in line: if name: blns[name] = (data, {}) data = {} start = line.find('[') end = line.find(']') if end < 0: end = len(line) name = line[start + 1:end] else: if ',' in line: key, value = [x.strip() for x in line.split(',', 1)] data[key] = value if name: blns[name] = (data, {}) return blns except: return None def finalize(self): for name in self.blns: data, bln = self.blns[name] for bln_id in bln.keys(): if bln[bln_id]: bln[bln_id].destroy() del bln[bln_id] self.blns = {} self.__sakura.detach_observer(self) return 1 def observer_update(self, event, args): ## FIXME if event == 'set scale': for name in self.blns: data, bln = self.blns[name] for bln_id in bln.keys(): if bln[bln_id]: bln[bln_id].reset_scale() def execute(self, argument): if not argument: return self.RESPONSE[400] name = argument[0] text = argument[1] if len(argument) >= 2 else '' if len(argument) >= 3: try: offset_x = int(argument[2]) except: offset_x = 0 else: offset_x = 0 if len(argument) >= 4: try: offset_y = int(argument[3]) except: offset_y = 0 else: offset_y = 0 bln_id = argument[4] if len(argument) >= 5 else '' if len(argument) >= 6 and argument[5] in ['1', '2']: update = int(argument[5]) else: update = 0 if name in self.blns: data, bln = self.blns[name] if bln_id in bln and update == 0: bln[bln_id].destroy() del bln[bln_id] if text: if update == 0 or bln_id not in bln: bln[bln_id] = Balloon(self.__sakura, self.dir, data, text, offset_x, offset_y, name, bln_id) else: bln[bln_id].update_script(text, update) self.blns[name] = (data, bln) elif name == 'clear': for name in self.blns: data, bln = self.blns[name] new_bln = {} for bln_id in bln: if bln[bln_id].get_state() == 'orusuban': new_bln[bln_id] = bln[bln_id] continue bln[bln_id].destroy() self.blns[name] = (data, new_bln) return None class Balloon(object): def __init__(self, sakura, dir, data, text, offset_x, offset_y, name, bln_id): self.dir = dir self.__sakura = sakura self.name = name self.id = bln_id self.data = data # XXX self.window = ninix.pix.TransparentWindow() self.window.set_focus_on_map(False) self.window.set_title(name) self.window.set_skip_taskbar_hint(True) self.window.set_decorated(False) self.window.set_resizable(False) self.window.connect('delete_event', self.delete) self.position = data.get('position', 'sakura') left, top, scrn_w, scrn_h = ninix.pix.get_workarea() # -1: left, 1: right if self.position == 'sakura': s0_x, s0_y, s0_w, s0_h = self.get_sakura_status('SurfaceSakura') self.direction = -1 if s0_x + s0_w / 2 > left + scrn_w / 2 else 1 elif self.position == 'kero': s1_x, s1_y, s1_w, s1_h = self.get_sakura_status('SurfaceKero') self.direction = -1 if s1_x + s1_w / 2 > left + scrn_w / 2 else 1 else: self.direction = 1 # XXX if self.position in ['sakura', 'kero']: if self.direction == -1: skin = data.get('skin.left', data.get('skin')) else: skin = data.get('skin.right', data.get('skin')) else: skin = data.get('skin') if skin is None: self.destroy() return path = os.path.join(self.dir, skin.replace('\\', '/')) try: balloon_pixbuf = ninix.pix.create_pixbuf_from_file(path) except: self.destroy() return self.balloon_pixbuf = balloon_pixbuf w = balloon_pixbuf.get_width() h = balloon_pixbuf.get_height() self.x = self.y = 0 if 'offset.x' in data: self.x += self.direction * int(data['offset.x']) if 'offset.y' in data: self.y += int(data['offset.y']) if 'offset.random' in data: self.x += int(data['offset.random']) * random.randrange(-1, 2) self.y += int(data['offset.random']) * random.randrange(-1, 2) self.x += self.direction * int(offset_x) self.y += int(offset_y) self.action_x = 0 self.action_y = 0 self.vx = 0 self.vy = 0 self.left = int(data.get('disparea.left', 0)) self.right = int(data.get('disparea.right', w)) self.top = int(data.get('disparea.top', 0)) self.bottom = int(data.get('disparea.bottom', h)) self.script = None self.darea = gtk.DrawingArea() self.darea.set_events(gtk.gdk.EXPOSURE_MASK| gtk.gdk.BUTTON_PRESS_MASK| gtk.gdk.BUTTON_RELEASE_MASK| gtk.gdk.POINTER_MOTION_MASK| gtk.gdk.LEAVE_NOTIFY_MASK) self.darea.connect('expose_event', self.redraw) self.darea.connect('button_press_event', self.button_press) self.darea.connect('button_release_event', self.button_release) self.darea.connect('motion_notify_event', self.motion_notify) self.darea.connect('leave_notify_event', self.leave_notify) self.darea.show() self.window.add(self.darea) self.darea.realize() self.window.realize() self.window.window.set_back_pixmap(None, False) self.set_skin() self.set_position() self.layout = None if text != 'noscript' and \ (self.right - self.left) and (self.bottom - self.top): self.script = text if 'font.color' in data: fontcolor_r = int(data['font.color'][:2], 16) / 255.0 fontcolor_g = int(data['font.color'][2:4], 16) /255.0 fontcolor_b = int(data['font.color'][4:6], 16) / 255.0 self.fontcolor = (fontcolor_r, fontcolor_g, fontcolor_b) else: self.fontcolor = (0.0, 0.0, 0.0) # XXX default_font_size = 12 # for Windows environment self.font_size = int(data.get('font.size', default_font_size)) self.layout = pango.Layout(self.darea.get_pango_context()) self.font_desc = pango.FontDescription() self.font_desc.set_family('Sans') # FIXME if data.get('font.bold') == 'on': self.font_desc.set_weight(pango.WEIGHT_BOLD) self.layout.set_wrap(pango.WRAP_CHAR) self.set_layout() self.slide_vx = int(data.get('slide.vx', 0)) self.slide_vy = int(data.get('slide.vy', 0)) self.slide_autostop = int(data.get('slide.autostop', 0)) if data.get('action.method') in ['sinwave', 'vibrate']: action = data['action.method'] ref0 = int(data.get('action.reference0', 0)) ref1 = int(data.get('action.reference1', 0)) ref2 = int(data.get('action.reference2', 0)) ref3 = int(data.get('action.reference3', 0)) if ref2: self.action = {'method': action, 'ref0': ref0, 'ref1': ref1, 'ref2': ref2, 'ref3': ref3} else: self.action = None else: self.action = None self.move_notify_time = None self.life_time = None self.state = '' life = data.get('life', 'auto') if life == 'auto': self.life_time = 16000 elif life in ['infinitely', '0']: pass elif life == 'orusuban': self.state = 'orusuban' else: try: self.life_time = int(life) except: pass self.start_time = time.time() self.startdelay = int(data.get('startdelay', 0)) self.nooverlap = int('nooverlap' in data) self.talking = self.get_sakura_is_talking() self.move_notify_time = time.time() self.timeout_id = None self.visible = 0 self.x_root = None self.y_root = None self.processed_script = None self.processed_text = '' self.text = '' self.script_wait = None self.quick_session = 0 self.script_parser = ninix.script.Parser(error='loose') try: self.processed_script = self.script_parser.parse(self.script) except ninix.script.ParserError as e: self.processed_script = None logging.error('-' * 50) logging.error(e) logging.error(self.script.encode('utf-8')) self.timeout_id = glib.timeout_add(10, self.do_idle_tasks) def set_position(self): if self.window is None: return new_x = self.base_x + int((self.x + self.action_x + self.vx) * self.scale / 100) new_y = self.base_y + int((self.y + self.action_y + self.vy) * self.scale / 100) self.window.resize_move(new_x, new_y) def set_skin(self): if self.window is None: return self.scale = self.get_sakura_status('SurfaceScale') ## FIXME balloon_pixbuf = self.balloon_pixbuf w = balloon_pixbuf.get_width() h = balloon_pixbuf.get_height() w = max(8, int(w * self.scale / 100)) h = max(8, int(h * self.scale / 100)) balloon_pixbuf = balloon_pixbuf.scale_simple( w, h, gtk.gdk.INTERP_BILINEAR) mask_pixmap = gtk.gdk.Pixmap(None, w, h, 1) balloon_pixbuf.render_threshold_alpha( mask_pixmap, 0, 0, 0, 0, w, h, 1) self.window.mask = mask_pixmap self.current_balloon_pixbuf = balloon_pixbuf self.darea.set_size_request(w, h) self.base_x, self.base_y = self.get_coordinate(w, h) def set_layout(self): if self.window is None: return if self.layout is None: return font_size = self.font_size * 3 / 4 # convert from Windows to GTK+ font_size = font_size * self.scale / 100 * pango.SCALE self.font_desc.set_size(font_size) self.layout.set_font_description(self.font_desc) self.layout.set_width( int((self.right - self.left) * 1024 * self.scale / 100)) def reset_scale(self): if self.window is None: return self.set_skin() self.set_position() self.set_layout() self.darea.queue_draw() @property def clickerase(self): return self.data.get('clickerase', 'on') == 'on' @property def dragmove_horizontal(self): return self.data.get('dragmove.horizontal') == 'on' @property def dragmove_vertical(self): return self.data.get('dragmove.vertical') == 'on' def get_coordinate(self, w, h): left, top, scrn_w, scrn_h = ninix.pix.get_workarea() s0_x, s0_y, s0_w, s0_h = self.get_sakura_status('SurfaceSakura') s1_x, s1_y, s1_w, s1_h = self.get_sakura_status('SurfaceKero') b0_x, b0_y, b0_w, b0_h = self.get_sakura_status('BalloonSakura') b1_x, b1_y, b1_w, b1_h = self.get_sakura_status('BalloonKero') x = left y = top if self.position == 'lefttop': pass elif self.position == 'leftbottom': y = top + scrn_h - h elif self.position == 'righttop': x = left + scrn_w - w elif self.position == 'rightbottom': x = left + scrn_w - w y = top + scrn_h - h elif self.position == 'center': x = left + (scrn_w - w) / 2 y = top + (scrn_h - h) / 2 elif self.position == 'leftcenter': y = top + (scrn_h - h) / 2 elif self.position == 'rightcenter': x = left + scrn_w - w y = top + (scrn_h - h) / 2 elif self.position == 'centertop': x = left + (scrn_w - w) / 2 elif self.position == 'centerbottom': x = left + (scrn_w - w) / 2 y = top + scrn_h - h elif self.position == 'sakura': if self.direction: # right x = s0_x + s0_w else: x = s0_x - w y = s0_y elif self.position == 'kero': if self.direction: # right x = s1_x + s1_w else: x = s1_x - w y = s1_y elif self.position == 'sakurab': x = b0_x y = b0_y elif self.position == 'kerob': x = b1_x y = b1_y return x, y def update_script(self, text, mode): if not text: return if mode == 2 and self.script is not None: self.script = ''.join((self.script, text)) else: self.script = text self.processed_script = None self.processed_text = '' self.text = '' self.script_wait = None self.quick_session = 0 try: self.processed_script = self.script_parser.parse(self.script) except ninix.script.ParserError as e: self.processed_script = None logging.error('-' * 50) logging.error(e) logging.error(self.script.encode('utf-8')) def get_sakura_is_talking(self): talking = 0 try: talking = int(self.__sakura.is_talking()) except: pass return talking def get_sakura_status(self, key): if key == 'SurfaceScale': result = self.__sakura.get_surface_scale() elif key == 'SurfaceSakura_Shown': result = int(self.__sakura.surface_is_shown(0)) elif key == 'SurfaceSakura': try: s0_x, s0_y = self.__sakura.get_surface_position(0) s0_w, s0_h = self.__sakura.get_surface_size(0) except: s0_x, s0_y = 0, 0 s0_w, s0_h = 0, 0 result = s0_x, s0_y, s0_w, s0_h elif key == 'SurfaceKero_Shown': result = int(self.__sakura.surface_is_shown(1)) elif key == 'SurfaceKero': try: s1_x, s1_y = self.__sakura.get_surface_position(1) s1_w, s1_h = self.__sakura.get_surface_size(1) except: s1_x, s1_y = 0, 0 s1_w, s1_h = 0, 0 result = s1_x, s1_y, s1_w, s1_h elif key == 'BalloonSakura_Shown': result = int(self.__sakura.balloon_is_shown(0)) elif key == 'BalloonSakura': try: b0_x, b0_y = self.__sakura.get_balloon_position(0) b0_w, b0_h = self.__sakura.get_balloon_size(0) except: b0_x, b0_y = 0, 0 b0_w, b0_h = 0, 0 result = b0_x, b0_y, b0_w, b0_h elif key == 'BalloonKero_Shown': result = int(self.__sakura.balloon_is_shown(1)) elif key == 'BalloonKero': try: b1_x, b1_y = self.__sakura.get_balloon_position(1) b1_w, b1_h = self.__sakura.get_balloon_size(1) except: b1_x, b1_y = 0, 0 b1_w, b1_h = 0, 0 result = b1_x, b1_y, b1_w, b1_h else: result = None return result def do_idle_tasks(self): if not self.window: return None s0_shown = self.get_sakura_status('SurfaceSakura_Shown') s1_shown = self.get_sakura_status('SurfaceKero_Shown') b0_shown = self.get_sakura_status('BalloonSakura_Shown') b1_shown = self.get_sakura_status('BalloonKero_Shown') sakura_talking = self.get_sakura_is_talking() if self.state == 'orusuban': if self.visible: if s0_shown or s1_shown: self.destroy() return None else: if not s0_shown and not s1_shown: self.start_time = time.time() self.visible = 1 self.window.show() self.life_time = 300000 else: if self.visible: if (self.position == 'sakura' and not s0_shown) or \ (self.position == 'kero' and not s1_shown) or \ (self.position == 'sakurab' and not b0_shown) or \ (self.position == 'kerob' and not b1_shown) or \ (self.nooverlap and not self.talking and sakura_talking): self.destroy() return None else: if time.time() - self.start_time >= self.startdelay * 0.001: self.start_time = time.time() self.visible = 1 self.window.show() if self.visible: if self.life_time: if time.time() - self.start_time >= self.life_time * 0.001 and \ not (self.processed_script or self.processed_text): self.destroy() return None if self.action: if self.action['method'] == 'sinwave': offset = self.action['ref1'] \ * math.sin(2.0 * math.pi * float(int((time.time() - \ self.start_time) * 1000) \ % self.action['ref2']) / self.action['ref2']) if self.action['ref0']: self.action_y = int(offset) else: self.action_x = int(offset) elif self.action['method'] == 'vibrate': offset = (int((time.time() - self.start_time) * 1000) / \ self.action['ref2']) % 2 self.action_x = int(offset * self.action['ref0']) self.action_y = int(offset * self.action['ref1']) if (self.slide_vx != 0 or self.slide_vy != 0) and \ self.slide_autostop > 0 and \ self.slide_autostop * 0.001 + 0.05 <= time.time() - self.start_time: self.vx = self.direction * int((self.slide_autostop / 50.0 + 1) * self.slide_vx) self.slide_vx = 0 self.vy = int((self.slide_autostop / 50.0 + 1) * self.slide_vy) self.slide_vy = 0 if self.slide_vx != 0: self.vx = self.direction * int(((time.time() - self.start_time) * self.slide_vx) / 50 * 1000.) if self.slide_vy != 0: self.vy = int(((time.time() - self.start_time) * self.slide_vy) / 50 * 1000.) self.set_position() if self.processed_script or self.processed_text: self.interpret_script() self.talking = 0 if self.talking and not sakura_talking else sakura_talking return True def redraw(self, darea, event): cr = darea.window.cairo_create() cr.save() cr.set_operator(cairo.OPERATOR_CLEAR) cr.paint() cr.restore() cr.set_source_pixbuf(self.current_balloon_pixbuf, 0, 0) # cr.paint_with_alpha(self.__alpha_channel) cr.paint() if self.layout: cr.set_source_rgb(*self.fontcolor) cr.move_to(int(self.left * self.scale / 100), int(self.top * self.scale / 100)) cr.show_layout(self.layout) del cr def get_state(self): return self.state def interpret_script(self): if self.script_wait is not None: if time.time() < self.script_wait: return self.script_wait = None if self.processed_text: if self.quick_session or self.state == 'orusuban': self.text = ''.join((self.text, self.processed_text)) self.draw_text(self.text) self.processed_text = '' else: self.text = ''.join((self.text, self.processed_text[0])) self.draw_text(self.text) self.processed_text = self.processed_text[1:] self.script_wait = time.time() + 0.014 return node = self.processed_script.pop(0) if node[0] == ninix.script.SCRIPT_TAG: name, args = node[1], node[2:] if name == r'\n': self.text = ''.join((self.text, '\n')) self.draw_text(self.text) elif name == r'\w': if args: try: amount = int(args[0]) * 0.05 - 0.01 except ValueError: amount = 0 else: amount = 1 * 0.05 - 0.01 if amount > 0: self.script_wait = time.time() + amount elif name == r'\b': if args: try: amount = int(args[0]) except ValueError: amount = 0 else: amount = 1 if amount > 0: self.text = self.text[:-amount] elif name == r'\c': self.text = '' elif name == r'\_q': self.quick_session = not self.quick_session elif name == r'\l': self.life_time = None self.update_script('', 2) elif node[0] == ninix.script.SCRIPT_TEXT: text = '' for chunk in node[1]: text = ''.join((text, chunk[1])) self.processed_text = text def draw_text(self, text): self.layout.set_text(text) self.darea.queue_draw() def button_press(self, widget, event): self.x_root = event.x_root self.y_root = event.y_root if event.type == gtk.gdk._2BUTTON_PRESS: x = int(event.x * 100 / self.scale) y = int(event.y * 100 / self.scale) self.__sakura.notify_event( 'OnEBMouseDoubleClick', self.name, x, y, self.id) return True def button_release(self, widget, event): self.x_root = None self.y_root = None x = int(event.x * 100 / self.scale) y = int(event.y * 100 / self.scale) if event.type == gtk.gdk.BUTTON_RELEASE: if event.button == 1: self.__sakura.notify_event( 'OnEBMouseClick', self.name, x, y, self.id, 0) elif event.button == 3: self.__sakura.notify_event( 'OnEBMouseClick', self.name, x, y, self.id, 1) if self.clickerase: self.destroy() return True def motion_notify(self, widget, event): scale = self.scale if self.x_root is not None and \ self.y_root is not None: x_delta = int(event.x_root - self.x_root) y_delta = int(event.y_root - self.y_root) if event.state & gtk.gdk.BUTTON1_MASK: if self.dragmove_horizontal: self.x += x_delta if self.dragmove_vertical: self.y += y_delta self.set_position() self.x_root = event.x_root self.y_root = event.y_root if self.move_notify_time is None or \ time.time() - self.move_notify_time > 500 * 0.001: x = int(event.x * 100 / scale) y = int(event.y * 100 / scale) self.__sakura.notify_event( 'OnEBMouseMove', self.name, x, y, self.id) self.move_notify_time = time.time() return True def leave_notify(self, widget, event): self.move_notify_time = None def delete(self, window, event): return True def destroy(self): self.visible = 0 if self.window: self.window.destroy() self.window = None if self.timeout_id: glib.source_remove(self.timeout_id) self.timeout_id = None ninix-aya-4.3.9/lib/ninix/dll/hanayu.py000066400000000000000000000510371172114553600177440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # hanayu.py - a "花柚" compatible Saori module for ninix # Copyright (C) 2002-2012 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 以外の形式のグラフへの対応. import sys import os import math import time import logging import gtk import pango import cairo import ninix.pix from ninix.dll import SAORI class Saori(SAORI): __DBNAME = 'HANAYU.db' def __init__(self): SAORI.__init__(self) self.graphs = {} self.data = {} def setup(self): self.dbpath = os.path.join(self.dir, self.__DBNAME) self.graphs = {} self.data = self.read_hanayu_txt(self.dir) if self.data: self.read_db() return 1 else: return 0 def read_hanayu_txt(self, dir): graphs = {} try: with open(os.path.join(dir, 'hanayu.txt'), 'r') as f: data = {} name = '' tmp_name = '' for line in f: line = line.strip() if not line: continue if line.startswith('//'): continue if line.startswith('['): # bln.txt like format graphs[name] = data data = {} end = line.find(']') if end < 0: end = len(line) name = line[1:end] elif line == '{': # surfaces.txt like format graphs[name] = data data = {} name = tmp_name ## FIXME elif line == '}': # surfaces.txt like format graphs[name] = data data = {} name = '' elif ',' in line: key, value = [x.strip() for x in line.split(',', 1)] data[key] = value elif not name: tmp_name = line if data: graphs[name] = data return graphs except: return None def finalize(self): for name in self.graphs.keys(): self.graphs[name].destroy() self.graphs = {} self.data = {} self.write_db() return 1 def time_to_key(self, secs, offset): year = int(time.strftime('%Y', time.localtime(secs))) month = int(time.strftime('%m', time.localtime(secs))) day = int(time.strftime('%d', time.localtime(secs))) target_time = time.mktime( (year, month, day + offset, 0, 0, 0, -1, -1, -1)) year = int(time.strftime('%Y', time.localtime(target_time))) month = int(time.strftime('%m', time.localtime(target_time))) day = int(time.strftime('%d', time.localtime(target_time))) key = str(year * 10000 + month * 100 + day) return key, year, month, day def read_db(self): self.seven_days = [] current_time = self.last_update = time.time() for index in range(-6, 1): key, year, month, day = self.time_to_key(current_time, index) self.seven_days.append([key, year, month, day, 0.0]) try: with open(self.dbpath) as f: ver = None for line in f: line = line.strip() if not line: continue if ver is None: if line == '# Format: v1.0': ver = 1 continue if ',' not in line: continue key, value = line.split(',', 1) for index in range(7): if self.seven_days[index][0] == key: self.seven_days[index][4] = float(value) except: return def update_db(self): current_time = time.time() old_seven_days = [] old_seven_days.extend(self.seven_days) self.seven_days = [] for index in range(-6, 1): key, year, month, day = self.time_to_key(current_time, index) self.seven_days.append([key, year, month, day, 0.0]) for i in range(7): key = old_seven_days[i][0] for j in range(7): if self.seven_days[j][0] == key: self.seven_days[j][4] = old_seven_days[i][4] if j == 6: self.seven_days[j][4] = self.seven_days[j][4] + \ (current_time - \ self.last_update) / \ (60.0 * 60.0) self.last_update = current_time def write_db(self): self.update_db() try: with open(self.dbpath, 'w') as f: f.write('# Format: v1.0\n') for index in range(7): f.write('{0}, {1}\n'.format( self.seven_days[index][0], self.seven_days[index][4])) except IOError: logging.error('HANAYU: cannot write database (ignored)') def execute(self, argument): if not argument: return self.RESPONSE[400] command = argument[0] if command == 'show': if len(argument) >= 2: name = argument[1] if name in self.graphs: self.graphs[name].destroy() else: name = '' if name not in self.data: return self.RESPONSE[400] if 'graph' in self.data[name] and \ self.data[name]['graph'] in ['line', 'bar', 'radar', 'radar2']: graph_type = self.data[name]['graph'] else: graph_type = 'usetime' if graph_type == 'usetime': self.update_db() new_args = [] for index in range(7): date = ''.join((str(self.seven_days[index][2]), '/', str(self.seven_days[index][3]))) new_args.append(date) hours = self.seven_days[index][4] new_args.append(hours) self.graphs[name] = Line( self.dir, self.data[name], new_args, 0, 24) elif graph_type == 'line': self.graphs[name] = Line( self.dir, self.data[name], argument[2:]) elif graph_type == 'bar': self.graphs[name] = Bar( self.dir, self.data[name], argument[2:]) elif graph_type == 'radar': self.graphs[name] = Radar( self.dir, self.data[name], argument[2:]) elif graph_type == 'radar2': self.graphs[name] = Radar2( self.dir, self.data[name], argument[2:]) elif command == 'hide': if len(argument) >= 2: name = argument[1] else: name = '' if name in self.graphs: self.graphs[name].destroy() else: return self.RESPONSE[400] else: return self.RESPONSE[400] return self.RESPONSE[204] class Graph(object): width = 450 height = 340 def __init__(self, dir, data, args=[], limit_min=None, limit_max=None): self.dir = dir self.data = data self.args = args self.min = limit_min self.max = limit_max self.create_window() self.window.show() def create_window(self): self.window = gtk.Window() self.window.set_title('花柚') # UTF-8 self.window.set_decorated(False) self.window.set_resizable(False) self.window.connect('delete_event', self.delete) left, top, scrn_w, scrn_h = ninix.pix.get_workarea() self.x = left + scrn_w // 2 self.y = top + scrn_h // 4 self.window.move(self.x, self.y) self.darea = gtk.DrawingArea() self.darea.set_events(gtk.gdk.EXPOSURE_MASK| gtk.gdk.BUTTON_PRESS_MASK) self.darea.connect('expose_event', self.redraw) self.darea.connect('button_press_event', self.button_press) self.darea.set_size_request(self.width, self.height) self.darea.show() self.window.add(self.darea) self.darea.realize() self.layout = pango.Layout(self.darea.get_pango_context()) pixbuf = None if 'background.filename' in self.data: path = os.path.join( self.dir, self.data['background.filename'].replace('\\', '/')) try: pixbuf = ninix.pix.create_pixbuf_from_file(path, is_pnr=0) except: pixbuf = None self.surface_pixbuf = pixbuf def get_color(self, target): assert target in ['font', 'line', 'frame', 'bar', 'background'] if target == 'background': r = g = b = 255 # white else: r = g = b = 0 # black name = ''.join((target, '.color')) if name in self.data: r = int(self.data[name][:2], 16) g = int(self.data[name][2:4], 16) b = int(self.data[name][4:6], 16) else: name_r = ''.join((name, '.r')) if name_r in self.data: r = int(self.data[name_r]) name_g = ''.join((name, '.g')) if name_g in self.data: g = int(self.data[name_g]) name_b = ''.join((name, '.b')) if name_b in self.data: b = int(self.data[name_b]) return (r / 255., g / 255., b / 255.) def draw_title(self): if 'title' in self.data: self.title = unicode(self.data['title'], 'Shift_JIS', 'ignore') font_size = 12 # pixel self.font_desc = pango.FontDescription() self.font_desc.set_family('Sans') # FIXME self.font_desc.set_size(font_size * pango.SCALE) cr = self.darea.window.cairo_create() cr.set_source_rgb(*self.get_color('font')) w, h = self.darea.window.get_size() cr.translate(w, 0) cr.rotate(math.pi / 2.0) layout = cr.create_layout() layout.set_font_description(self.font_desc) context = layout.get_context() default_gravity = context.get_gravity() # XXX context.set_base_gravity(pango.GRAVITY_EAST) # Vertical Text layout.set_text(self.title) layout.set_wrap(pango.WRAP_WORD) tw, th = layout.get_pixel_size() cr.move_to(58, w - 20 - th) cr.show_layout(layout) context.set_base_gravity(default_gravity) # XXX del cr def draw_frame(self): pass def draw_graph(self): pass def redraw(self, darea, event): cr = darea.window.cairo_create() cr.set_source_rgb(*self.get_color('background')) cr.paint() if self.surface_pixbuf: width = self.surface_pixbuf.get_width() height = self.surface_pixbuf.get_height() xoffset = (self.width - width) // 2 yoffset = (self.height - height) // 2 cr.set_source_pixbuf(self.surface_pixbuf, xoffset, yoffset) cr.paint() del cr self.draw_title() self.draw_frame() self.draw_graph() def button_press(self, window, event): if event.type == gtk.gdk.BUTTON_PRESS: self.window.begin_move_drag( event.button, int(event.x_root), int(event.y_root), event.time) elif event.type == gtk.gdk._2BUTTON_PRESS: # double click self.destroy() return True def delete(self, window, event): return True def destroy(self): if self.window: self.window.destroy() self.window = None self.timeout_id = None class Line(Graph): def draw_frame(self): frame_width = 2 if 'frame.width' in self.data: frame_width = int(self.data['frame.width']) cr = self.darea.window.cairo_create() cr.set_line_width(frame_width) cr.set_source_rgb(*self.get_color('frame')) cr.move_to(60, 48) cr.line_to(60, 260) cr.line_to(420, 260) cr.stroke() del cr def draw_graph(self): cr = self.darea.window.cairo_create() cr.set_source_rgb(*self.get_color('font')) num = len(self.args) // 2 step = 368 // num for index in range(num): self.layout.set_text(self.args[index * 2]) w, h = self.layout.get_pixel_size() pos_x = 60 + index * step + step // 2 - w // 2 pos_y = 268 cr.move_to(pos_x, pos_y) cr.show_layout(self.layout) if self.min is not None: limit_min = self.min else: limit_min = self.args[1] for index in range(2, num): if self.args[index * 2] < limit_min: limit_min = self.args[index * 2] if self.max is not None: limit_max = self.max else: limit_max = self.args[1] for index in range(2, num): if self.args[index * 2] > limit_max: limit_max = self.args[index * 2] line_width = 2 if 'line.width' in self.data: line_width = int(self.data['line.width']) cr.set_line_width(line_width) cr.set_source_rgb(*self.get_color('line')) for index in range(1, num): src_x = 60 + (index - 1) * step + step // 2 src_y = 220 - int( 168 * self.args[(index - 1) * 2 + 1] / (limit_max - limit_min)) dst_x = 60 + index * step + step // 2 dst_y = 220 - int(168 * self.args[index * 2 + 1] / (limit_max - limit_min)) cr.move_to(src_x, src_y) cr.line_to(dst_x, dst_y) cr.stroke() for index in range(num): pixbuf = None if self.args[index * 2 + 1] == limit_min and \ 'mark0.filename' in self.data: path = os.path.join( self.dir, self.data['mark0.filename'].replace('\\', '/')) if os.path.exists(path): try: pixbuf = ninix.pix.create_pixbuf_from_file(path) except: pixbuf = None elif self.args[index * 2 + 1] == limit_max and \ 'mark2.filename' in self.data: path = os.path.join( self.dir, self.data['mark2.filename'].replace('\\', '/')) if os.path.exists(path): try: pixbuf = ninix.pix.create_pixbuf_from_file(path) except: pixbuf = None elif 'mark1.filename' in self.data: path = os.path.join( self.dir, self.data['mark1.filename'].replace('\\', '/')) if os.path.exists(path): try: pixbuf = ninix.pix.create_pixbuf_from_file(path) except: pixbuf = None if pixbuf: w = pixbuf.get_width() h = pixbuf.get_height() x = 60 + index * step + step // 2 - w // 2 y = 220 - int( 168 * self.args[index * 2 + 1] / (limit_max - limit_min)) - h / 2 cr.set_source_pixbuf(pixbuf, x, y) cr.paint() del cr class Bar(Graph): def draw_frame(self): frame_width = 2 if 'frame.width' in self.data: frame_width = int(self.data['frame.width']) cr = self.darea.window.cairo_create() cr.set_line_width(frame_width) cr.set_source_rgb(*self.get_color('frame')) cr.move_to(60, 48) cr.line_to(60, 260) cr.line_to(420, 260) cr.stroke() del cr def draw_graph(self): ## FIXME cr = self.darea.window.cairo_create() cr.set_source_rgb(*self.get_color('bar')) bar_with = 20 ## FIXME if 'bar.width' in self.data: bar_width = int(self.data['bar.width']) ### NOT YET ### class Radar(Graph): width = 288 height = 288 def __init__(self, dir, data, args=[]): Graph.__init__(self, dir, data, args) def draw_frame(self): frame_width = 2 if 'frame.width' in self.data: frame_width = int(self.data['frame.width']) cr = self.darea.window.cairo_create() cr.set_line_width(frame_width) cr.set_source_rgb(*self.get_color('frame')) num = len(self.args) // 2 for index in range(num): x = 146 + int(math.cos(math.pi * (0.5 - 2.0 * index / num)) * 114) y = 146 - int(math.sin(math.pi * (0.5 - 2.0 * index / num)) * 114) cr.move_to(146, 146,) cr.line_to(x, y) cr.stroke() del cr def draw_graph(self): num = len(self.args) // 2 for index in range(num): try: value = self.args[index * 2 + 1] self.args[index * 2 + 1] = float(value) except: self.args[index * 2 + 1] = 0.0 if self.args[index * 2 + 1] < 0: self.args[index * 2 + 1] = 0.0 limit_min = self.args[1] for index in range(num): if self.args[index * 2 + 1] < limit_min: limit_min = self.args[index * 2 + 1] limit_max = self.args[1] for index in range(num): if self.args[index * 2 + 1] > limit_max: limit_max = self.args[index * 2 + 1] line_width = 2 if 'line.width' in self.data: line_width = int(self.data['line.width']) cr = self.darea.window.cairo_create() cr.set_line_width(line_width) cr.set_source_rgb(*self.get_color('line')) if limit_max > 0: value = self.args[(num - 1) * 2 + 1] / limit_max else: value = 1.0 src_x = 146 + int(math.cos( math.pi * (0.5 - 2.0 * (num - 1) / num)) * value * 100) src_y = 146 - int(math.sin( math.pi * (0.5 - 2.0 * (num - 1) / num)) * value * 100) cr.move_to(src_x, src_y) for index in range(num): if limit_max > 0: value = self.args[index * 2 + 1] / limit_max else: value = 1.0 dst_x = 146 + int( math.cos(math.pi * (0.5 - 2.0 * index / num)) * value * 100) dst_y = 146 - int( math.sin(math.pi * (0.5 - 2.0 * index / num)) * value * 100) cr.line_to(dst_x, dst_y) cr.stroke() font_size = 9 # pixel self.font_desc.set_size(font_size * pango.SCALE) self.layout.set_font_description(self.font_desc) cr.set_source_rgb(*self.get_color('font')) for index in range(num): ##if limit_max > 0: ## value = self.args[index * 2 + 1] / limit_max ##else: ## value = 1.0 value = 1.2 # XXX x = 146 + int(math.cos( math.pi * (0.5 - 2.0 * index / num)) * value * 100) y = 146 - int(math.sin( math.pi * (0.5 - 2.0 * index / num)) * value * 100) self.layout.set_text(self.args[index * 2]) w, h = self.layout.get_pixel_size() x -= w // 2 y -= h // 2 cr.move_to(x, y) cr.show_layout(self.layout) del cr class Radar2(Graph): width = 288 height = 288 def __init__(self, dir, data, args=[]): Graph.__init__(self, dir, data, args) if __name__ == '__main__': USAGE= 'Usage: hanayu.py ' if len(sys.argv) == 2: saori = Saori() data = saori.read_hanayu_txt(sys.argv[1]) print data else: print USAGE ninix-aya-4.3.9/lib/ninix/dll/httpc.py000066400000000000000000000127031172114553600175760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # httpc.py - a HTTPC compatible Saori module for ninix # Copyright (C) 2011, 2012 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. # import httplib import urlparse import glib try: import chardet except: chardet = None from ninix.dll import SAORI class Saori(SAORI): def __init__(self): SAORI.__init__(self) self.__sakura = None self.__bg = {} def finalize(self): for timeout_id in self.__bg: glib.source_remove(timeout_id) return 1 def need_ghost_backdoor(self, sakura): self.__sakura = sakura def check_import(self): return int(self.__sakura is not None and chardet is not None) def get(self, url, start=None, end=None): url = urlparse.urlparse(url) if not (url[0] == 'http' and url[3] == url[4] == url[5] == ''): return self.RESPONSE[400] conn = httplib.HTTPConnection(url[1]) conn.request("GET", url[2]) res = conn.getresponse() if res.status != 200: # XXX return [] content = res.read() c_type = res.getheader('content-type') if c_type: for x in c_type.split(';'): if x.strip().startswith('charset='): self.charset = x[9:] break # XXX else: pass ## FIXME: check META http-equiv if self.charset is None: ## FIXME: respect explicit character encoding information self.charset = chardet.detect(content)['encoding'] if self.charset is None: return [] data = unicode(content, self.charset, 'ignore') conn.close() if start is not None: assert end is not None nc = 0 ls = len(start) le = len(end) result = [] while 1: ns = data.find(start, nc) if ns < 0: break ns += ls ne = data.find(end, ns) if ne < 0: break nc = ne + le result.append(data[ns:ne]) else: result = [data] return result def execute(self, argument): bg = None self.charset = None process_tag = None if len(argument) >= 1: if argument[0] == 'bg': if len(argument) < 2: # 'bgするならIDを指定していただけませんと。' return self.RESPONSE[400] bg = argument[1] argument = argument[2:] if len(argument) >= 1: if argument[0] in ['sjis', 'utf-8', 'utf-16be', 'utf-16le']: self.charset = argument[0] argument = argument[1:] elif argument[0] =='euc': self.charset = 'EUC-JP' argument = argument[1:] elif argument[0] =='jis': self.charset = 'ISO-2022-JP ' argument = argument[1:] if argument[0] == 'erase_tag': def erase_tag(data): ## FIXME: not supported yet pass process_tag = erase_tag argument = argument[1:] elif argument[0] == 'translate_tag': def translate_tag(data): ## FIXME: not supported yet pass process_tag = translate_tag argument = argument[1:] if not argument: ##assert bg is None and process_tag is None return 'SAORI/1.0 200 OK\r\n' \ 'Result: {0}\r\n\r\n'.format(self.loaded) # XXX elif len(argument) > 3: return self.RESPONSE[400] elif len(argument) == 2: # FIXME: not supported yet return 'SAORI/1.0 200 OK\r\n' \ 'Result: 0\r\n\r\n' else: if bg: # needs multi-threading? timeout_id = glib.timeout_add(1000, # XXX self.notify, bg, argument, process_tag) self.__bg[timeout_id] = bg return None # 'SAORI/1.0 204 No Content\r\n\r\n' else: data = self.get(*argument) if not data: return None # 'SAORI/1.0 204 No Content\r\n\r\n' result = 'SAORI/1.0 200 OK\r\n' \ 'Result: {0}\r\n'.format( \ data[0].encode('Shift_JIS', 'replace')) for n in range(len(data)): result = ''.join(( result, 'Value{0:d}: {1}\r\n'.format( n, data[n].encode('Shift_JIS', 'replace')))) result += '\r\n' return result def notify(self, id, argument, process_tag): result = self.get(*argument) if process_tag is not None: pass ## FIXME self.__sakura.notify_event('OnHttpcNotify', id, None, *result) ninix-aya-4.3.9/lib/ninix/dll/kawari.py000066400000000000000000002045341172114553600177370ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # kawari.py - a "華和梨" compatible Shiori module for ninix # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import base64 import os import re import time import random import sys import logging import codecs from ninix.home import get_normalized_path ### READER ### charset = 'Shift_JIS' # default def read_dict(path): global charset with open(path) as f: lineno = 0 buf = [] for line in f: lineno = lineno + 1 position = (lineno, path) if line.startswith('!KAWA0000'): line = decrypt(line[9:]) if not line.strip() or \ line.startswith('#') or \ line.startswith(':crypt') or \ line.startswith(':endcrypt'): continue if ':' not in line: logging.debug('kawari.py: syntax error at line {0:d} in {1}:'.format(*position)) logging.debug(line.strip()) continue entries, phrases = [x.strip() for x in line.split(':', 1)] if not entries: logging.debug('kawari.py: syntax error at line {0:d} in {1}:'.format(*position)) logging.debug(line.strip()) continue if not phrases: continue if entries == 'locale': try: codecs.lookup(phrases) except: logging.error('kawari.py: unsupported charset {0}'.format(phrases)) else: charset = phrases else: entries = unicode(entries, charset, 'ignore') phrases = unicode(phrases, charset, 'ignore') buf.append((entries, phrases, position)) return buf def decrypt(data): buf = [] for c in base64.decodestring(data): buf.append(chr(ord(c) ^ 0xcc)) return ''.join(buf) def encrypt(data): buf = [] for c in data: buf.append(chr(ord(c) ^ 0xcc)) line = ''.join(('!KAWA0000', base64.encodestring(''.join(buf)))) return line.replace('\n', '') def create_dict(buf): rdict = {} # rules kdict = {} # keywords for entries, phrases, position in buf: parsed_entries = parse_entries(entries) parsed_phrases = parse_phrases(phrases) if parsed_phrases is None: logging.debug('kawari.py: syntax error at line {0:d} in {1}:'.format(*position)) logging.debug(phrases.strip().encode('utf-8', 'ignore')) continue if entries.startswith('['): add_phrases(kdict, tuple(parsed_entries), parsed_phrases) continue for entry in parsed_entries: add_phrases(rdict, entry, parsed_phrases) return rdict, kdict def add_phrases(dic, entry, phrases): if entry not in dic: dic[entry] = [] for phrase in phrases: if phrase: phrase[0] = phrase[0].lstrip() phrase[-1] = phrase[-1].rstrip() dic[entry].append(tuple(phrase)) def parse_entries(data): if data.startswith('[') and data.endswith(']'): entries = [] i = 0 j = len(data) while i < j: if data[i] == '"': i, text = parse_quotes(data, i) entries.append(text) else: i += 1 else: entries = [s.strip() for s in data.split(',')] return entries re_comma = re.compile(',') def parse_phrases(data): buf = [] i = 0 j = len(data) while i < j: if data[i] == ',': i += 1 i, phrase = parse(data, i, re_comma) if phrase: buf.append(phrase) return buf def parse(data, start, stop_pattern=None): buf = [] i = start j = len(data) while i < j: if stop_pattern and stop_pattern.match(data, i): break elif data[i] == '"': i, text = parse_quotes(data, i) buf.append(u'"{0}"'.format(text)) elif data[i:i + 2] == '${': i, text = parse_reference(data, i) buf.append(text) elif data[i:i + 2] == '$(': i, text = parse_inline_script(data, i) buf.append(text) elif data[i] == '$': buf.append(data[i]) i += 1 elif data[i] == ';': buf.append(data[i]) i += 1 else: i, text = parse_text(data, i, stop_pattern) buf.append(text) if buf: if is_space(buf[0]): del buf[0] else: buf[0] = buf[0].lstrip() if buf: if is_space(buf[-1]): del buf[-1] else: buf[-1] = buf[-1].rstrip() return i, buf def parse_quotes(data, start): buf = [] i = start + 1 j = len(data) while i < j: if data[i] == '"': i += 1 break elif data[i] == '\\': i += 1 if i < j and data[i] == '"': buf.append(''.join(('\\', data[i]))) i += 1 else: buf.append('\\') else: buf.append(data[i]) i += 1 return i, ''.join(buf) def parse_reference(data, start): i = start j = len(data) while i < j: if data[i] == '}': i += 1 break else: i += 1 return i, data[start:i] def parse_inline_script(data, start): buf = ['$'] i = start + 1 j = len(data) npar = 0 while i < j: #begin specification bug work-around (1/3) if data[i] == ')': buf.append(data[i]) i += 1 break #end if data[i] == '"': i, text = parse_quotes(data, i) buf.append(u'"{0}"'.format(text)) elif data[i:i + 2] == '${': i, text = parse_reference(data, i) buf.append(text) elif data[i:i + 2] == '$(': i, text = parse_inline_script(data, i) buf.append(text) else: if data[i] == '(': npar = npar + 1 elif data[i] == ')': npar = npar - 1 buf.append(data[i]) i += 1 if npar == 0: break return i, ''.join(buf) def is_space(s): return not s.strip() def parse_text(data, start, stop_pattern=None): condition = is_space(data[start]) i = start j = len(data) while i < j: if stop_pattern and stop_pattern.match(data, i): break elif data[i] in ['$', '"']: break elif is_space(data[i]) != condition: break elif data[i] == ';': if i == start: i += 1 break else: i += 1 return i, data[start:i] def read_local_script(path): rdict = {} kdict = {} with open(path) as f: for line in f.readlines(): if line.startswith('#'): rdict[unicode(line.strip(), charset, 'ignore')] = \ [unicode(f.readline().strip(), charset, 'ignore')] return rdict, kdict ### KAWARI ### class Kawari(object): MAXDEPTH = 30 def __init__(self, prefix, pathlist, rdictlist, kdictlist): self.prefix = prefix self.pathlist = pathlist self.rdictlist = rdictlist self.kdictlist = kdictlist self.system_entries = {} self.expr_parser = ExprParser() #begin specification bug work-around (2/3) self.expr_parser.kawari = self #end self.otherghost = {} self.get_system_entry('OnLoad') def finalize(self): self.get_system_entry('OnUnload') # SHIORI/1.0 API def getaistringrandom(self): return self.get('sentence').encode(charset, 'ignore') def getaistringfromtargetword(self, word): word = unicode(word, charset, 'ignore') return self.get('sentence').encode(charset, 'ignore') # XXX def getdms(self): return self.getword('dms') def getword(self, word_type): for delimiter in ['.', '-']: name = ''.join(('compatible', delimiter, word_type)) script = self.get(name, default=None) if script is not None: return script.strip().encode(charset, 'ignore') return '' def getstring(self, name): name = unicode(name, charset, 'ignore') return self.get(u'resource.{0}'.format(name)).encode(charset, 'ignore') # SHIORI/2.2 API def get_event_response(self, event, ref0=None, ref1=None, ref2=None, ref3=None, ref4=None, ref5=None, ref6=None, ref7=None): ## FIXME def proc(ref): if ref is not None: ref = unicode(str(ref), charset, 'ignore') return ref ref = [proc(ref) for ref in [ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7]] for i in range(8): if ref[i] is not None: value = ref[i] self.system_entries['system.Reference{0:d}'.format(i)] = value self.system_entries['system-Reference{0:d}'.format(i)] = value script = None if event == 'OnCommunicate': self.system_entries['system.Sender'] = ref[0] self.system_entries['system-Sender'] = ref[0] self.system_entries['system.Sender.Path'] = 'local' # (local/unknown/external) self.system_entries['system-Sender.Path'] = 'local' # (local/unknown/external) if 'system.Age' not in self.system_entries: self.system_entries['system.Age'] = '0' self.system_entries['system-Age'] = '0' self.system_entries['system.Sentence'] = ref[1] self.system_entries['system-Sentence'] = ref[1] if ref[0] in self.otherghost: s0, s1 = self.otherghost[ref[0]] self.system_entries['system.Surface'] = ','.join((str(s0), str(s1))) self.system_entries['system-Surface'] = ','.join((str(s0), str(s1))) script = self.get_system_entry('OnResponse') if not script: for dic in self.kdictlist: for entry in dic: for word in entry: if word not in ref[1]: break else: script = self.expand(random.choice(dic[entry])) break if not script: script = self.get_system_entry('OnResponseUnknown') if script is not None: script = script.strip() else: for delimiter in ['.', '-']: name = ''.join(('event', delimiter, event)) script = self.get(name, default=None) if script is not None: script = script.strip() break if script is not None: script = script.encode(charset, 'ignore') return script # SHIORI/2.4 API def teach(self, word): word = unicode(word, charset, 'ignore') self.system_entries['system.Sentence'] = word self.system_entries['system-Sentence'] = word return self.get_system_entry('OnTeach').encode(charset, 'ignore') def get_system_entry(self, entry): for delimiter in ['.', '-']: name = ''.join(('system', delimiter, entry)) script = self.get(name, default=None) if script is not None: return script.strip() return None def otherghostname(self, ghost_list): ghosts = [] for ghost in ghost_list: name, s0, s1 = ghost.split(chr(1)) ghosts.append([unicode(name, charset, 'ignore'), s0, s1]) self.otherghost[name] = [s0, s1] otherghost_name = [] for ghost in ghosts: otherghost_name.append(ghost[0]) self.system_entries['system.OtherGhost'] = otherghost_name self.system_entries['system-OtherGhost'] = otherghost_name otherghost_ex = [] for ghost in ghosts: otherghost_ex.append(chr(1).join(ghost)) self.system_entries['system.OtherGhostEx'] = otherghost_ex self.system_entries['system-OtherGhostEx'] = otherghost_ex if ghosts: self.get_system_entry('OnNotifyOther') return '' def communicate_to(self): communicate = self.get_system_entry('communicate') if communicate: if communicate == 'stop': self.system_entries['system.Age'] = '0' self.system_entries['system-Age'] = '0' communicate = None else: if 'system.Age' in self.system_entries: age = int(self.system_entries['system.Age']) + 1 self.system_entries['system.Age'] = str(age) self.system_entries['system-Age'] = str(age) else: self.system_entries['system.Age'] = '0' self.system_entries['system-Age'] = '0' self.clear('system.communicate') if communicate is not None: communicate = communicate.encode(charset, 'ignore') return communicate # internal def clear(self, name): logging.debug('*** clear("{0}")'.format(name.encode('utf-8', 'ignore'))) for dic in self.rdictlist: if name in dic: del dic[name] def get_internal_dict(self, name): for dic in self.rdictlist: if name in dic: break else: dic = self.rdictlist[0] dic[name] = [] return dic def unshift(self, name, value): logging.debug( (u'*** unshift("{0}", "{1}")'.format(name, value)).encode('utf-8', 'ignore')) dic = self.get_internal_dict(name) i, segments = parse(value, 0) dic[name].insert(0, segments) def shift(self, name): dic = self.get_internal_dict(name) value = self.expand(dic[name].pop(0)) logging.debug( (u'*** shift("{0}") => "{1}"'.format(name, value)).encode('utf-8', 'ignore')) return value def push(self, name, value): logging.debug( (u'*** push("{0}", "{1}")'.format(name, value)).encode('utf-8', 'ignore')) dic = self.get_internal_dict(name) i, segments = parse(value, 0) dic[name].append(segments) def pop(self, name): dic = self.get_internal_dict(name) value = self.expand(dic[name].pop()) logging.debug( (u'*** pop("{0}") => "{1}"'.format(name, value)).encode('utf-8', 'ignore')) return value def set(self, name, value): self.clear(name) self.push(name, value) def get(self, name, context=None, depth=0, default=''): if depth == self.MAXDEPTH: return '' if name and name.startswith('@'): segments = random.choice(context[name]) else: if '&' not in name: selection = self.select_simple_phrase(name) else: selection = self.select_compound_phrase(name) if selection is None: logging.debug('${{{0}}} not found'.format(name.encode('utf-8', 'ignore'))) return default segments, context = selection logging.debug( ''.join((name.encode('utf-8', 'ignore'), '=>', ''.join(segments).encode('utf-8', 'ignore')))) return self.expand(segments, context, depth) def parse_all(self, data, start=0): i, segments = parse(data, start) return self.expand(segments) def parse_sub(self, data, start=0, stop_pattern=None): i, segments = parse(data, start, stop_pattern) return i, self.expand(segments) def expand(self, segments, context=None, depth=0): buf = [] references = [] i = 0 j = len(segments) while i < j: segment = segments[i] if not segment: pass elif segment.startswith('${') and segment.endswith('}'): newname = segment[2:-1] if self.is_number(newname): try: segment = references[int(newname)] except IndexError: pass elif self.is_system_entry(newname): if newname in ['system.OtherGhost', 'system-OtherGhost', 'system.OtherGhostEx', 'system-OtherGhostEx']: segment_list = self.system_entries.get(newname) if segment_list: segment = random.choice(segment_list) else: segment = '' elif newname == 'system.communicate': segment = self.get_system_entry('communicate') else: segment = self.system_entries.get(newname, segment) else: segment = self.get(newname, context, depth + 1) references.append(segment) elif segment.startswith('$(') and segment.endswith(')'): i, segment = self.eval_inline_script(segments, i) elif segment.startswith('"') and segment.endswith('"'): segment = segment[1:-1].replace(r'\"', '"') buf.append(segment) i += 1 return ''.join(buf) def atoi(self, s): try: return int(s) except ValueError: return 0 def is_number(self, s): try: int(s) except ValueError: return 0 return 1 def is_system_entry(self, s): return s.startswith('system-') or s.startswith('system.') def select_simple_phrase(self, name): n = 0 buf = [] for d in self.rdictlist: c = d.get(name, []) n += len(c) buf.append((c, d)) if n == 0: return None n = random.randrange(0, n) for c, d in buf: m = len(c) if n < m: break n -= m return c[n], d def select_compound_phrase(self, name): buf = [] for name in [s.strip() for s in name.split('&')]: cp_list = [] for d in self.rdictlist: cp_list.extend([tuple(e) for e in d.get(name, [])]) buf.append(cp_list) buf[:] = [(len(x), x) for x in buf] buf.sort() buf[:] = [x for len_x, x in buf] candidates = [] for item in buf.pop(0): for cp_list in buf: if item not in cp_list: break else: candidates.append(item) if not candidates: return None return random.choice(candidates), None def eval_inline_script(self, segments, i): # check old 'if' syntax if segments[i].startswith('$(if ') and i + 1 < len(segments) and \ segments[i + 1].startswith('$(then '): if_block = segments[i][5:-1].strip() i += 1 then_block = segments[i][7:-1].strip() if i + 1 < len(segments) and segments[i + 1].startswith('$(else '): i += 1 else_block = segments[i][7:-1].strip() else: else_block = '' if i + 1 < len(segments) and segments[i + 1] == '$(endif)': i += 1 else: logging.debug('kawari.py: syntax error: $(endif) expected') return i, '' # syntax error return i, self.exec_old_if(if_block, then_block, else_block) # execute command(s) values = [] for command in self.split_commands(segments[i][2:-1]): argv = self.parse_argument(command) argv[0] = self.expand(argv[0]) if argv[0] == 'silent': if len(argv) == 1: values = [] else: logging.debug( ''.join(('kawari.py: syntax error:', segments[i].encode('utf-8', 'ignore')))) continue handler = self.kis_commands.get(argv[0]) try: if handler is None: raise RuntimeError, 'invalid command' values.append(handler(self, argv)) except RuntimeError as message: logging.debug( 'kawari.py: {0}: {1}'.format( message, segments[i].encode('utf-8', 'ignore'))) result = ''.join(values) logging.debug( ''.join(('>>>', segments[i].encode('utf-8', 'ignore')))) logging.debug( ''.join(('"', result.encode('utf-8', 'ignore'), '"'))) return i, result def split_commands(self, data): i, segments = parse(data, 0) # find multiple commands separated by semicolons buf = [] command = [] for segment in segments: if segment == ';': if command: buf.append(command) command = [] else: command.append(segment) if command: buf.append(command) # strip white space before and after each command for command in buf: if is_space(command[0]): del command[0] if is_space(command[-1]): del command[-1] return buf def parse_argument(self, segments): buf = [[]] for segment in segments: if is_space(segment): buf.append([]) else: buf[-1].append(segment) return buf def exec_new_if(self, argv): if len(argv) == 3: return self.exec_if(argv[1], argv[2], None) elif len(argv) == 4: return self.exec_if(argv[1], argv[2], argv[3]) else: raise RuntimeError, 'syntax error' def exec_old_if(self, if_block, then_block, else_block): # convert [...] into $([...]) if if_block and if_block.startswith('[') and if_block.endswith(']'): if_block = ''.join(('$(', if_block, ')')) # parse arguments i, if_block = parse(if_block, 0) i, then_block = parse(then_block, 0) if else_block: i, else_block = parse(else_block, 0) result = self.exec_if(if_block, then_block, else_block) if else_block: logging.debug( '>>> $(if {0})$(then {1})$(else {2})$(endif)'.format( ''.join(if_block).encode('utf-8', 'ignore'), ''.join(then_block).encode('utf-8', 'ignore'), ''.join(else_block).encode('utf-8', 'ignore'))) else: logging.debug( '>>> $(if {0})$(then {1})$(endif)'.format( ''.join(if_block).encode('utf-8', 'ignore'), ''.join(then_block).encode('utf-8', 'ignore'))) logging.debug( ''.join(('"', result.encode('utf-8', 'ignore'), '"'))) return result def exec_if(self, if_block, then_block, else_block): if self.expand(if_block) not in ['', '0', 'false', 'False']: return self.expand(then_block) elif else_block: return self.expand(else_block) return '' def exec_foreach(self, argv): if len(argv) != 4: raise RuntimeError, 'syntax error' temp = self.expand(argv[1]) name = self.expand(argv[2]) buf = [] for dic in self.rdictlist: for segments in dic.get(name, []): self.set(temp, self.expand(segments)) buf.append(self.expand(argv[3])) self.clear(temp) return ''.join(buf) def exec_loop(self, argv): if len(argv) != 3: raise RuntimeError, 'syntax error' try: n = int(self.expand(argv[1])) except ValueError: raise RuntimeError, 'invalid argument' buf = [] for _ in range(n): buf.append(self.expand(argv[2])) return ''.join(buf) def exec_while(self, argv): if len(argv) != 3: raise RuntimeError, 'syntax error' buf = [] while self.expand(argv[1]) not in ['', '0', 'false', 'False']: buf.append(self.expand(argv[2])) return ''.join(buf) def exec_until(self, argv): if len(argv) != 3: raise RuntimeError, 'syntax error' buf = [] while self.expand(argv[1]) in ['', '0', 'false', 'False']: buf.append(self.expand(argv[2])) return ''.join(buf) def exec_set(self, argv): if len(argv) != 3: raise RuntimeError, 'syntax error' self.set(self.expand(argv[1]), self.expand(argv[2])) return '' def exec_adddict(self, argv): if len(argv) != 3: raise RuntimeError, 'syntax error' self.push(self.expand(argv[1]), self.expand(argv[2])) return '' def exec_array(self, argv): # XXX experimental if len(argv) != 3: raise RuntimeError, 'syntax error' name = self.expand(argv[1]) n = self.atoi(self.expand(argv[2])) for d in self.rdictlist: c = d.get(name, []) if n < len(c): return ''.join([self.expand(s) for s in c[n]]) n -= len(c) else: raise RuntimeError, 'invalid argument' def exec_clear(self, argv): if len(argv) != 2: raise RuntimeError, 'syntax error' self.clear(self.expand(argv[1])) return '' def exec_enumerate(self, argv): if len(argv) != 2: raise RuntimeError, 'syntax error' name = self.expand(argv[1]) return ' '.join([self.expand(s) for s in self.enumerate(name)]) def enumerate(self, name): buf = [] for dic in self.rdictlist: for segments in dic.get(name, []): buf.append(segments) return buf def exec_size(self, argv): if len(argv) != 2: raise RuntimeError, 'syntax error' name = self.expand(argv[1]) n = 0 for d in self.rdictlist: c = d.get(name, []) n += len(c) return str(n) def exec_get(self, argv): # XXX experimental if len(argv) != 3: raise RuntimeError, 'syntax error' name = self.expand(argv[1]) n = self.atoi(self.expand(argv[2])) for d in self.rdictlist: c = d.get(name, []) if n < len(c): return ''.join(c[n]) n -= len(c) else: raise RuntimeError, 'invalid argument' def exec_unshift(self, argv): if len(argv) != 3: raise RuntimeError, 'syntax error' self.unshift(self.expand(argv[1]), self.expand(argv[2])) return '' def exec_shift(self, argv): if len(argv) != 2: raise RuntimeError, 'syntax error' return self.shift(self.expand(argv[1])) def exec_push(self, argv): if len(argv) != 3: raise RuntimeError, 'syntax error' self.push(self.expand(argv[1]), self.expand(argv[2])) return '' def exec_pop(self, argv): if len(argv) != 2: raise RuntimeError, 'syntax error' return self.pop(self.expand(argv[1])) def exec_pirocall(self, argv): if len(argv) != 2: raise RuntimeError, 'syntax error' selection = self.select_simple_phrase(self.expand(argv[1])) if selection is None: return '' return selection[0] def exec_split(self, argv): if len(argv) != 4: raise RuntimeError, 'syntax error' name = self.expand(argv[1]) word_list = self.expand(argv[2]).split(self.expand(argv[3])) n = 0 for word in word_list: n += 1 entry = '{0}.{1:d}'.format(name, n) self.set(entry, word) self.set(''.join((name, '.size')), str(n)) return '' def get_dict_path(self, path): path = get_normalized_path(path, encode=0) if not path: raise RuntimeError, 'invalid argument' if path.startswith('/'): return path return os.path.join(self.prefix, path) def exec_load(self, argv): if len(argv) != 2: raise RuntimeError, 'syntax error' path = self.get_dict_path(self.expand(argv[1])) try: rdict, kdict = create_dict(read_dict(path)) except IOError: raise RuntimeError, 'cannot read file' if path in self.pathlist: i = self.pathlist.index(path) self.rdictlist[i].update(rdict) self.kdictlist[i].update(kdict) else: self.pathlist.insert(0, path) self.rdictlist.insert(0, rdict) self.kdictlist.insert(0, kdict) return '' def exec_save(self, argv, crypt=0): if len(argv) < 2: raise RuntimeError, 'syntax error' path = self.get_dict_path(self.expand(argv[1])) try: with open(path, 'w') as f: f.write('#\r\n# Kawari save file\r\n#\r\n') for i in range(2, len(argv)): name = self.expand(argv[i]) if not name.strip(): continue buf = [] for segments in self.enumerate(name): buf.append(''.join(segments)) name = name.encode(charset, 'ignore') line = ''.join((name, ' : ', ' , '.join(buf).encode(charset, 'ignore'))) if crypt: line = encrypt(line) f.write('# Entry {0}\r\n{1}\r\n'.format(name, line)) except IOError: raise RuntimeError, 'cannot write file' return '' def exec_savecrypt(self, argv): return self.exec_save(argv, 1) def exec_textload(self, argv): if len(argv) != 3: raise RuntimeError, 'syntax error' path = self.get_dict_path(self.expand(argv[1])) try: with open(path) as f: linelist = f.readlines() except IOError: raise RuntimeError, 'cannot read file' name = self.expand(argv[2]) n = 0 for line in linelist: n += 1 entry = '{0}.{1:d}'.format(name, n) if line.endswith('\r\n'): line = line[:-2] elif line.endswith('\r') or line.endswith('\n'): line = line[:-1] if not line: self.clear(entry) else: self.set(entry, unicode(line, charset, 'replace')) self.set(''.join((name, '.size')), str(n)) return '' def exec_escape(self, argv): data = ' '.join([self.expand(s) for s in argv[1:]]) data = data.replace('\\', r'\\') data = data.replace('%', r'\%') return data def exec_echo(self, argv): return ' '.join([self.expand(s) for s in argv[1:]]) def exec_tolower(self, argv): return self.exec_echo(argv).lower() def exec_toupper(self, argv): return self.exec_echo(argv).upper() def exec_eval(self, argv): return self.parse_all(self.exec_echo(argv)) def exec_entry(self, argv): if len(argv) == 2: return self.get(self.expand(argv[1])) elif len(argv) == 3: return self.get(self.expand(argv[1])) or self.expand(argv[2]) else: raise RuntimeError, 'syntax error' def exec_null(self, argv): if len(argv) != 1: raise RuntimeError, 'syntax error' return '' def exec_chr(self, argv): if len(argv) != 2: raise RuntimeError, 'syntax error' num = self.atoi(self.expand(argv[1])) if num < 256: return chr(num) return ''.join((chr((num >> 8) & 0xff), chr(num & 0xff))) def exec_choice(self, argv): if len(argv) == 1: return '' i = random.randrange(1, len(argv)) return self.expand(argv[i]) def exec_rand(self, argv): if len(argv) != 2: raise RuntimeError, 'syntax error' bound = self.atoi(self.expand(argv[1])) if bound == 0: return str(0) elif bound > 0: return str(random.randrange(0, bound)) else: return str(random.randint(bound + 1, 0)) def exec_date(self, argv): if len(argv) == 1: format_ = '%y/%m/%d %H:%M:%S' else: format_ = ' '.join([self.expand(s) for s in argv[1:]]) buf = [] i = 0 j = len(format_) now = time.localtime(time.time()) while i < j: if format_[i] == '%': i += 1 if i < j: c = format_[i] i += 1 else: break if c in ['y', 'Y']: # year (4 columns) buf.append('{0:04d}'.format(now[0])) elif c == 'm': # month (01 - 12) buf.append('{0:02d}'.format(now[1])) elif c == 'n': # month (1 - 12) buf.append(str(now[1])) elif c == 'd': # day (01 - 31) buf.append('{0:02d}'.format(now[2])) elif c == 'e': # day (1 - 31) buf.append(str(now[2])) elif c == 'H': # hour (00 - 23) buf.append('{0:02d}'.format(now[3])) elif c == 'k': # hour (0 - 23) buf.append(str(now[3])) elif c == 'M': # minute (00 - 59) buf.append('{0:02d}'.format(now[4])) elif c == 'N': # minute (0 - 59) buf.append(str(now[4])) elif c == 'S': # second (00 - 59) buf.append('{0:02d}'.format(now[5])) elif c == 'r': # second (0 - 59) buf.append(str(now[5])) elif c == 'w': # weekday (0 = Sunday) buf.append(str([1, 2, 3, 4, 5, 6, 0][now[6]])) elif c == 'j': # Julian day (001 - 366) buf.append('{0:03d}'.format(now[7])) elif c == 'J': # Julian day (1 - 366) buf.append(str(now[7])) elif c == '%': buf.append('%') else: buf.append('%') i -= 1 else: buf.append(format_[i]) i += 1 return ''.join(buf) def exec_inc(self, argv): def _inc(value, step, bound): value += step if bound is not None and value > bound: return bound return value self.apply_counter_op(_inc, argv) return '' def exec_dec(self, argv): def _dec(value, step, bound): value -= step if bound is not None and value < bound: return bound return value self.apply_counter_op(_dec, argv) return '' def apply_counter_op(self, func, argv): if len(argv) < 2 or len(argv) > 4: raise RuntimeError, 'syntax error' name = self.expand(argv[1]) value = self.atoi(self.get(name)) if len(argv) >= 3: step = self.atoi(self.expand(argv[2])) else: step = 1 if len(argv) == 4: bound = self.atoi(self.expand(argv[3])) else: bound = None self.set(name, str(func(value, step, bound))) def exec_test(self, argv): if argv[0] == 'test' and len(argv) == 4 or \ argv[0] == '[' and len(argv) == 5 and self.expand(argv[4]) == ']': op1 = self.expand(argv[1]) op = self.expand(argv[2]) op2 = self.expand(argv[3]) else: raise RuntimeError, 'syntax error' if op in ['=', '==']: result = str(op1 == op2) elif op == '!=': result = str(op1 != op2) elif op == '<=': result = str(op1 <= op2) elif op == '>=': result = str(op1 >= op2) elif op == '<': result = str(op1 < op2) elif op == '>': result = str(op1 > op2) elif op == '-eq': result = str(self.atoi(op1) == self.atoi(op2)) elif op == '-ne': result = str(self.atoi(op1) != self.atoi(op2)) elif op == '-le': result = str(self.atoi(op1) <= self.atoi(op2)) elif op == '-ge': result = str(self.atoi(op1) >= self.atoi(op2)) elif op == '-lt': result = str(self.atoi(op1) < self.atoi(op2)) elif op == '-gt': result = str(self.atoi(op1) > self.atoi(op2)) else: raise RuntimeError, 'unknown operator' return result def exec_expr(self, argv): tree = self.expr_parser.parse( ' '.join([''.join(e) for e in argv[1:]])) if tree is None: raise RuntimeError, 'syntax error' try: value = self.interp_expr(tree) except ExprError: raise RuntimeError, 'runtime error' return value def interp_expr(self, tree): if tree[0] == ExprParser.OR_EXPR: for subtree in tree[1:]: value = self.interp_expr(subtree) if value and value not in ['0', 'False']: break return value elif tree[0] == ExprParser.AND_EXPR: buf = [] for subtree in tree[1:]: value = self.interp_expr(subtree) if not value or value in ['0', 'False']: return '0' buf.append(value) return buf[0] elif tree[0] == ExprParser.CMP_EXPR: op1 = self.interp_expr(tree[1]) op2 = self.interp_expr(tree[3]) if self.is_number(op1) and self.is_number(op2): op1 = int(op1) op2 = int(op2) if tree[2] in ['=', '==']: return str(op1 == op2) elif tree[2] == '!=': return str(op1 != op2) elif tree[2] == '<=': return str(op1 <= op2) elif tree[2] == '>=': return str(op1 >= op2) elif tree[2] == '<': return str(op1 < op2) elif tree[2] == '>': return str(op1 > op2) else: raise RuntimeError, 'unknown operator' elif tree[0] == ExprParser.ADD_EXPR: for i in range(1, len(tree), 2): tree[i] = self.interp_expr(tree[i]) if not self.is_number(tree[i]): raise ExprError value = int(tree[1]) for i in range(2, len(tree), 2): if tree[i] == '+': value += int(tree[i + 1]) elif tree[i] == '-': value -= int(tree[i + 1]) return str(value) elif tree[0] == ExprParser.MUL_EXPR: for i in range(1, len(tree), 2): tree[i] = self.interp_expr(tree[i]) if not self.is_number(tree[i]): raise ExprError value = int(tree[1]) for i in range(2, len(tree), 2): if tree[i] == '*': value *= int(tree[i + 1]) elif tree[i] == '/': try: value /= int(tree[i + 1]) except ZeroDivisionError: raise ExprError elif tree[i] == '%': try: value %= int(tree[i + 1]) except ZeroDivisionError: raise ExprError return str(value) elif tree[0] == ExprParser.STR_EXPR: if tree[1] == 'length': length = len(self.get_characters(self.interp_expr(tree[2]))) return str(length) elif tree[1] == 'index': s = self.get_characters(self.interp_expr(tree[2])) c = self.get_characters(self.interp_expr(tree[3])) for pos in range(len(s)): if s[pos] in c: break else: pos = 0 return str(pos) elif tree[1] == 'match': try: match = re.match(self.interp_expr(tree[3]), self.interp_expr(tree[2])) except re.error: match = None if match: length = match.end() - match.start() else: length = 0 return str(length) elif tree[1] == 'find': s = self.interp_expr(tree[3]) if self.interp_expr(tree[2]).find(s) < 0: return '' return s elif tree[1] == 'findpos': s = self.interp_expr(tree[3]) pos = self.interp_expr(tree[2]).find(s); if pos < 0: return '' return str(pos + 1) elif tree[1] == 'substr': s = self.interp_expr(tree[2]) p = self.interp_expr(tree[3]) n = self.interp_expr(tree[4]) if self.is_number(p) and self.is_number(n): p = int(p) - 1 n = p + int(n) if 0 <= p <= n: characters = self.get_characters(s) return ''.join(characters[p:n]) return '' elif tree[0] == ExprParser.LITERAL: return self.expand(tree[1:]) def get_characters(self, s): buf = [] i = 0 j = len(s) while i < j: buf.append(s[i]) i += 1 return buf 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': None, 'chr': exec_chr, 'help': None, 'ver': None, 'searchghost': None, 'saoriregist': None, 'saorierase': None, 'callsaori': None, 'callsaorix': None, } ### EXPR PARSER ### class ExprError(ValueError): pass class ExprParser(object): def __init__(self): pass def show_progress(self, func, buf): if buf is None: logging.debug('{0}() -> syntax error'.format(func)) else: logging.debug('{0}() -> {1}'.format(func, buf)) re_token = re.compile('[():|&*/%+-]|[<>]=?|[!=]?=|match|index|findpos|find|substr|length|quote|(\\s+)') def tokenize(self, data): buf = [] i = 0 j = len(data) while i < j: match = self.re_token.match(data, i) if match: buf.append(match.group()) i = match.end() else: i, segments = parse(data, i, self.re_token) buf.extend(segments) return buf def parse(self, data): self.tokens = self.tokenize(data) try: return self.get_expr() except ExprError: return None # syntax error # internal def done(self): return not self.tokens def pop(self): try: return self.tokens.pop(0) except IndexError: raise ExprError def look_ahead(self, index=0): try: return self.tokens[index] except IndexError: raise ExprError def match(self, s): if self.pop() != s: raise ExprError def match_space(self): if not is_space(self.pop()): raise ExprError def check(self, s, index=0): return self.look_ahead(index) == s def check_space(self, index=0): return is_space(self.look_ahead(index)) # 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(self): buf = self.get_or_expr() if not self.done(): raise ExprError self.show_progress('get_expr', buf) return buf def get_or_expr(self): buf = [self.OR_EXPR] while 1: buf.append(self.get_and_expr()) if not self.done() and \ self.check_space() and self.check('|', 1): self.pop() # space self.pop() # operator self.match_space() else: break if len(buf) == 2: buf = buf[1] self.show_progress('get_or_expr', buf) return buf def get_and_expr(self): buf = [self.AND_EXPR] while 1: buf.append(self.get_cmp_expr()) if not self.done() and \ self.check_space() and self.check('&', 1): self.pop() # space self.pop() # operator self.match_space() else: break if len(buf) == 2: buf = buf[1] self.show_progress('get_and_expr', buf) return buf def get_cmp_expr(self): buf = [self.CMP_EXPR] buf.append(self.get_add_expr()) if not self.done() and \ self.check_space() and \ self.look_ahead(1) in ['<=', '>=', '<', '>', '=', '==', '!=']: self.pop() # space buf.append(self.pop()) # operator self.match_space() buf.append(self.get_add_expr()) if len(buf) == 2: buf = buf[1] self.show_progress('get_cmp_expr', buf) return buf def get_add_expr(self): buf = [self.ADD_EXPR] while 1: buf.append(self.get_mul_expr()) if not self.done() and \ self.check_space() and self.look_ahead(1) in ['+', '-']: self.pop() # space buf.append(self.pop()) # operator self.match_space() else: break if len(buf) == 2: buf = buf[1] self.show_progress('get_add_expr', buf) return buf def get_mul_expr(self): buf = [self.MUL_EXPR] while 1: buf.append(self.get_mat_expr()) if not self.done() and \ self.check_space() and self.look_ahead(1) in ['*', '/', '%']: self.pop() # space buf.append(self.pop()) # operator self.match_space() else: break if len(buf) == 2: buf = buf[1] self.show_progress('get_mul_expr', buf) return buf def get_mat_expr(self): buf = [self.STR_EXPR] buf.append(self.get_str_expr()) if not self.done() and \ self.check_space() and self.check(':', 1): buf.insert(1, 'match') self.pop() # space self.pop() # ':' self.match_space() buf.append(self.get_str_expr()) if len(buf) == 2: buf = buf[1] self.show_progress('get_mat_expr', buf) return buf def get_str_expr(self): argc = 0 if self.check('length'): argc = 1 elif self.look_ahead() in ['match', 'index', 'find', 'findpos']: argc = 2 elif self.check('substr'): argc = 3 if argc > 0: buf = [self.STR_EXPR, self.pop()] # fuction for _ in range(argc): self.match_space() buf.append(self.get_str_expr()) elif self.check('quote'): buf = [self.LITERAL] self.pop() self.match_space() if self.re_token.match(self.look_ahead()): buf.append(self.pop()) else: buf.extend(self.get_str_seq()) else: buf = self.get_sub_expr() self.show_progress('get_str_expr', buf) return buf def get_sub_expr(self): if self.check('('): self.pop() if self.check_space(): self.pop() buf = self.get_or_expr() if self.check_space(): self.pop() #begin specification bug work-around (3/3) self.tokens[0] = self.kawari.parse_all(self.tokens[0]) #end self.match(')') else: buf = [self.LITERAL] buf.extend(self.get_str_seq()) self.show_progress('get_sub_expr', buf) return buf def get_str_seq(self): buf = [] while not self.done() and \ not self.re_token.match(self.look_ahead()): buf.append(self.pop()) if not buf: raise ExprError return buf # <<< 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 = list(range(2)) def list_dict(kawari_dir, saori_ini={}): return scan_ini(kawari_dir, 'kawari.ini', saori_ini) def scan_ini(kawari_dir, filename, saori_ini): buf = [] ini_path = os.path.join(kawari_dir, filename) try: line_list = read_dict(ini_path) except IOError: line_list = [] read_as_dict = 0 for entry, value, position in line_list: if entry == 'dict': filename = get_normalized_path(value) path = os.path.join(kawari_dir, filename) try: with open(path) as f: f.read(64) except IOError as e: errno, message = e.args logging.debug('kawari.py: read error: {0}'.format(path)) continue buf.append((DICT_FILE, path)) elif entry == 'include': filename = get_normalized_path(value) buf.extend(scan_ini(kawari_dir, filename, saori_ini)) elif entry == 'set': read_as_dict = 1 elif entry in ['randomseed', 'debug', 'security', 'set']: pass elif entry == 'saori': saori_list = value.split(',') path = saori_list[0].strip().encode('utf-8') alias = saori_list[1].strip() if len(saori_list) == 3: option = saori_list[2].strip() else: option = 'loadoncall' saori_ini[alias] = [path, option] else: logging.debug('kawari.py: unknown entry: {0}'.format(entry)) if read_as_dict: buf.append((INI_FILE, ini_path)) return buf def read_ini(path): buf = [] try: line_list = read_dict(path) except IOError: line_list = [] for entry, value, position in line_list: if entry == 'set': try: entry, value = value.split(None, 1) except ValueError: continue buf.append((entry.strip(), value.strip(), position)) return buf class Shiori(Kawari): def __init__(self, dll_name): self.dll_name = dll_name self.saori_list = {} self.saori_ini = {} self.kis_commands['saoriregist'] = self.exec_saoriregist self.kis_commands['saorierase'] = self.exec_saorierase self.kis_commands['callsaori'] = self.exec_callsaori self.kis_commands['callsaorix'] = self.exec_callsaorix def use_saori(self, saori): self.saori = saori def load(self, kawari_dir): self.kawari_dir = kawari_dir pathlist = [None] rdictlist = [{}] kdictlist = [{}] self.saori_ini = {} for file_type, path in list_dict(kawari_dir, self.saori_ini): pathlist.append(path) if file_type == INI_FILE: rdict, kdict = create_dict(read_ini(path)) elif is_local_script(path): rdict, kdict = read_local_script(path) else: rdict, kdict = create_dict(read_dict(path)) rdictlist.append(rdict) kdictlist.append(kdict) Kawari.__init__(self, kawari_dir, pathlist, rdictlist, kdictlist) for value in self.saori_ini.values(): if value[1] == 'preload': head, tail = os.path.split(value[0].replace('\\', '/')) self.saori_load(value[0], os.path.join(self.kawari_dir, head)) return 1 def unload(self): Kawari.finalize(self) for name in self.saori_list.keys(): self.saori_list[name].unload() del self.saori_list[name] global charset charset = 'Shift_JIS' # reset def find(self, dir, dll_name): result = 0 if list_dict(dir): result = 200 global charset charset = 'Shift_JIS' # reset return result def show_description(self): 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-2012 by Shyouzou Sugitani\n' ' Copyright (C) 2003 by Shun-ichi TAHARA') def request(self, req_string): header = req_string.splitlines() req_header = {} line = header.pop(0) if line: line = line.strip() req_list = line.split() if len(req_list) >= 2: command = req_list[0].strip() protocol = req_list[1].strip() for line in header: line = line.strip() if not line: continue if ':' not in line: continue key, value = [x.strip() for x in line.split(':', 1)] try: value = int(value) except: value = str(value) req_header[key] = value result = '' to = None if 'ID' in req_header: if req_header['ID'] == 'dms': result = self.getdms() elif req_header['ID'] == 'OnAITalk': result = self.getaistringrandom() elif req_header['ID'] in ['\\ms', '\\mz', '\\ml', '\\mc', '\\mh', \ '\\mt', '\\me', '\\mp']: result = self.getword(req_header['ID'][1:]) elif req_header['ID'] == '\\m?': result = self.getword('m') elif req_header['ID'] == 'otherghostname': otherghost = [] for n in range(128): key = ''.join(('Reference', str(n))) if key in req_header: otherghost.append(req_header[key]) result = self.otherghostname(otherghost) elif req_header['ID'] == 'OnTeach': if 'Reference0' in req_header: self.teach(req_header['Reference0']) else: result = self.getstring(req_header['ID']) if not result: ref = [] for n in range(8): key = ''.join(('Reference', str(n))) if key in req_header: ref.append(req_header[key]) else: ref.append(None) ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7 = ref result = self.get_event_response( req_header['ID'], ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7) if result is None: result = '' to = self.communicate_to() result = 'SHIORI/3.0 200 OK\r\n' \ 'Sender: Kawari\r\n' \ 'Charset: {0}\r\n' \ 'Value: {1}\r\n'.format(charset, result) if to is not None: result = ''.join((result, 'Reference0: {0}\r\n'.format(to))) result = ''.join((result, '\r\n')) return result def exec_saoriregist(self, kawari, argv): filename = self.expand(argv[1]) alias = self.expand(argv[2]) if len(argv) == 4: option = self.expand(argv[3]) else: option = 'loadoncall' self.saori_ini[alias] = [filename, option] if self.saori_ini[alias][1] == 'preload': head, tail = os.path.split( self.saori_ini[alias][0].replace('\\', '/')) self.saori_load(self.saori_ini[alias][0], os.path.join(self.kawari_dir, head)) return '' def exec_saorierase(self, kawari, argv): alias = self.expand(argv[1]) if alias in self.saori_ini: self.saori_unload(self.saori_ini[alias][0]) return '' def exec_callsaori(self, kawari, argv): alias = self.expand(argv[1]) if alias not in self.saori_ini: return '' if self.saori_ini[alias][0] not in self.saori_list: if self.saori_ini[alias][1] == 'preload': return '' else: head, tail = os.path.split( self.saori_ini[alias][0].replace('\\', '/')) self.saori_load(self.saori_ini[alias][0], os.path.join(self.kawari_dir, head)) saori_statuscode = '' saori_header = [] saori_value = {} saori_protocol = '' req = 'EXECUTE SAORI/1.0\r\n' \ 'Sender: KAWARI\r\n' \ 'SecurityLevel: local\r\n' \ 'Charset: {0}\r\n'.format(charset) for i in range(2, len(argv)): req = ''.join((req, u'Argument{0}: {1}\r\n'.format(i - 2, self.expand(argv[i])))) req = ''.join((req, '\r\n')) response = self.saori_request(self.saori_ini[alias][0], req.encode(charset, 'ignore')) header = response.splitlines() line = header.pop(0) if line: line = line.strip() if ' ' in line: saori_protocol, saori_statuscode = [x.strip() for x in line.split(' ', 1)] for line in header: line = line.strip() if not line: continue if ':' not in line: continue key, value = [x.strip() for x in line.split(':', 1)] if key: saori_header.append(key) saori_value[key] = value if 'Result' in saori_value: result = saori_value['Result'] else: result = '' if self.saori_ini[alias][1] == 'noresident': self.saori_unload(self.saori_ini[alias][0]) return result def exec_callsaorix(self, kawari, argv): alias = self.expand(argv[1]) entry = self.expand(argv[2]) if alias not in self.saori_ini: return '' if self.saori_ini[alias][0] not in self.saori_list: if self.saori_ini[alias][1] == 'preload': return '' else: head, tail = os.path.split( self.saori_ini[alias][0].replace('\\', '/')) self.saori_load(self.saori_ini[alias][0], os.path.join(self.kawari_dir, head)) 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 range(3, len(argv)): req = ''.join((req, u'Argument{0}: {1}\r\n'.format(i - 3, self.expand(argv[i])))) req = ''.join((req, '\r\n')) response = self.saori_request(self.saori_ini[alias][0], req.encode(charset, 'ignore')) header = response.splitlines() line = header.pop(0) if line: line = line.strip() if ' ' in line: saori_protocol, saori_statuscode = [x.strip() for x in line.split(' ', 1)] for line in header: line = line.strip() if not line: continue if ':' not in line: continue key, value = [x.strip() for x in line.split(':', 1)] if key: saori_header.append(key) saori_value[key] = value result = {} for key, value in saori_value.items(): if key.startswith('Value'): result[key] = value for key, value in result.items(): self.set(''.join((entry, '.', key)), value) if self.saori_ini[alias][1] == 'noresident': self.saori_unload(self.saori_ini[alias][0]) return len(result) def saori_load(self, saori, path): result = 0 if saori in self.saori_list.keys(): result = self.saori_list[saori].load(path) else: module = self.saori.request(saori) if module: self.saori_list[saori] = module result = self.saori_list[saori].load(path) return result def saori_unload(self, saori): result = 0 if saori in self.saori_list.keys(): result = self.saori_list[saori].unload() return result def saori_request(self, saori, req): result = 'SAORI/1.0 500 Internal Server Error' if saori in self.saori_list: result = self.saori_list[saori].request(req) return result def kawari_open(kawari_dir): pathlist = [None] rdictlist = [{}] kdictlist = [{}] for file_type, path in list_dict(kawari_dir): pathlist.append(path) if file_type == INI_FILE: rdict, kdict = create_dict(read_ini(path)) elif is_local_script(path): rdict, kdict = read_local_script(path) else: rdict, kdict = create_dict(read_dict(path)) rdictlist.append(rdict) kdictlist.append(kdict) return Kawari(kawari_dir, pathlist, rdictlist, kdictlist) def is_local_script(path): with open(path) as f: line = f.readline() return line.startswith('[SAKURA]') ### TEST ### def test(): logger = logging.getLogger() logger.setLevel(logging.DEBUG) # XXX if len(sys.argv) >= 2: kawari_dir = sys.argv[1] else: kawari_dir = os.curdir print 'reading kawari.ini...' kawari = kawari_open(kawari_dir) for rdict in kawari.rdictlist: for k, v in rdict.items(): print k.encode('utf-8', 'ignore') for p in v: print ''.join(('\t', ''.join(p).encode('utf-8', 'ignore'))) for kdict in kawari.kdictlist: for k, v in kdict.items(): print ''.join( ('[ "', '", "'.join(k).encode('utf-8', 'ignore'), '" ]')) for p in v: print ''.join(('\t', ''.join(p).encode('utf-8', 'ignore'))) while 1: print '=' * 40 s = kawari.getaistringrandom() print '-' * 40 print unicode(s, charset, 'ignore').encode('utf-8', 'ignore') try: raw_input() except (EOFError, KeyboardInterrupt): break if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/dll/kawari8.py000066400000000000000000000077561172114553600200360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # kawari8.py - a (Real) 華和梨 loader for ninix # Copyright (C) 2002, 2003 by ABE Hideaki # Copyright (C) 2002-2012 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. # import os import sys import logging try: import _kawari8 except: _kawari8 = None class Shiori(object): saori_list = {} def __init__(self, dll_name): self.dll_name = dll_name self.handle = 0 def use_saori(self, saori): self.saori = saori def find(self, topdir, dll_name): result = 0 if _kawari8 and os.path.isfile(os.path.join(topdir, 'kawarirc.kis')): result = 205 return result def show_description(self): logging.info( 'Shiori: Real Kawari8 loader for ninix\n' ' Copyright (C) 2002, 2003 by ABE Hideaki\n' ' Copyright (C) 2002-2012 by Shyouzou Sugitani\n' ' Copyright (C) 2002, 2003 by MATSUMURA Namihiko') def load(self, topdir): self.dir = topdir if _kawari8: ##reload(_kawari8) if self.dir.endswith(os.sep): topdir = self.dir else: topdir = ''.join((self.dir, os.sep)) _kawari8.setcallback(self.saori_exist, Shiori.saori_load, Shiori.saori_unload, Shiori.saori_request) result = _kawari8.load(topdir) self.handle = result return 1 if result != 0 else 0 else: return 0 def unload(self): if _kawari8: _kawari8.unload(self.handle) for name in Shiori.saori_list.keys(): if not name.startswith(self.dir): continue if self.saori_list[name][1]: self.saori_list[name][0].unload() del self.saori_list[name] # XXX _kawari8.setcallback(lambda *a: 0, # dummy Shiori.saori_load, Shiori.saori_unload, Shiori.saori_request) def request(self, req_string): if _kawari8: return _kawari8.request(self.handle, req_string) else: return '' # FIXME 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 = '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(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 ninix-aya-4.3.9/lib/ninix/dll/mciaudio.py000066400000000000000000000066061172114553600202530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # mciaudio.py - a MCIAUDIO compatible Saori module for ninix # Copyright (C) 2002-2012 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. # import os import urllib import logging try: ##import pygst ##pygst.require('0.10') # This breaks sys.path. :-( import gst except: gst = None from ninix.home import get_normalized_path from ninix.dll import SAORI class Saori(SAORI): def __init__(self): SAORI.__init__(self) self.player = gst.element_factory_make('playbin', 'player') fakesink = gst.element_factory_make('fakesink', 'fakesink') self.player.set_property('video-sink', fakesink) bus = self.player.get_bus() bus.add_signal_watch() bus.connect('message', self.on_message) self.filepath = None self.__sakura = None self.state = 0 # 0/1/2 - stop/playing/paused def need_ghost_backdoor(self, sakura): self.__sakura = sakura def check_import(self): return 1 if self.__sakura is not None and gst is not None else 0 def finalize(self): self.player.set_state(gst.STATE_NULL) self.player = None self.filepath = None return 1 def execute(self, argv): argc = len(argv) if argc == 1: assert self.player is not None if argv[0] == 'stop': self.player.set_state(gst.STATE_NULL) elif argv[0] == 'play': if self.state == 2: self.state = 1 self.player.set_state(gst.STATE_PLAYING) return self.RESPONSE[204] elif self.state == 1: self.state = 2 self.player.set_state(gst.STATE_PAUSED) return self.RESPONSE[204] if self.filepath is not None and os.path.isfile(self.filepath): self.player.set_property( 'uri', 'file://' + urllib.quote(self.filepath)) self.player.set_state(gst.STATE_PLAYING) elif argc == 2: if argv[0] == 'load': self.player.set_state(gst.STATE_NULL) filename = get_normalized_path(argv[1]) if os.path.isabs(filename): return self.RESPONSE[400] self.filepath = os.path.join(self.__sakura.get_prefix(), 'ghost/master', self.dir, filename) return self.RESPONSE[204] def on_message(self, bus, message): t = message.type if t == gst.MESSAGE_EOS: self.player.set_state(gst.STATE_NULL) self.state = 0 elif t == gst.MESSAGE_ERROR: self.player.set_state(gst.STATE_NULL) err, debug = message.parse_error() logging.error('Error: {0}, {1}'.format(err, debug)) self.state = 0 ninix-aya-4.3.9/lib/ninix/dll/mciaudior.py000066400000000000000000000064651172114553600204400ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # mciaudior.py - a MCIAUDIOR compatible Saori module for ninix # Copyright (C) 2003-2012 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. # import os import urllib import logging try: ##import pygst ##pygst.require('0.10') # This breaks sys.path. :-( import gst except: gst = None from ninix.home import get_normalized_path from ninix.dll import SAORI class Saori(SAORI): def __init__(self): SAORI.__init__(self) self.player = gst.element_factory_make('playbin', 'player') fakesink = gst.element_factory_make('fakesink', 'fakesink') self.player.set_property('video-sink', fakesink) bus = self.player.get_bus() bus.add_signal_watch() bus.connect('message', self.on_message) self.filepath = None self.state = 0 # 0/1/2 - stop/playing/paused self.loop = False def check_import(self): return 1 if gst is not None else 0 def finalize(self): self.player.set_state(gst.STATE_NULL) self.player = None self.filepath = None return 1 def execute(self, argv): argc = len(argv) if argc == 1: assert self.player is not None if argv[0] == 'stop': self.player.set_state(gst.STATE_NULL) elif argv[0] in ['play', 'loop']: if argv[0] == 'loop': self.loop = True if self.state == 2: self.state = 1 self.player.set_state(gst.STATE_PLAYING) return self.RESPONSE[204] elif self.state == 1: self.state = 2 self.player.set_state(gst.STATE_PAUSED) return self.RESPONSE[204] if self.filepath is not None and os.path.isfile(self.filepath): self.player.set_property( 'uri', 'file://' + urllib.quote(self.filepath)) self.player.set_state(gst.STATE_PLAYING) elif argc == 2: if argv[0] == 'load': self.player.set_state(gst.STATE_NULL) filename = get_normalized_path(argv[1]) self.filepath = os.path.join(self.dir, filename) return self.RESPONSE[204] def on_message(self, bus, message): t = message.type if t == gst.MESSAGE_EOS: if self.loop: self.player.set_state(gst.STATE_PLAYING) self.state = 1 else: self.player.set_state(gst.STATE_NULL) self.state = 0 elif t == gst.MESSAGE_ERROR: self.player.set_state(gst.STATE_NULL) err, debug = message.parse_error() logging.error('Error: {0}, {1}'.format(err, debug)) self.state = 0 self.loop = False ninix-aya-4.3.9/lib/ninix/dll/misaka.py000066400000000000000000002370221172114553600177240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # misaka.py - a "美坂" compatible Shiori module for ninix # Copyright (C) 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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) ? import os import re import StringIO import sys import logging import time import random try: import chardet.universaldetector except: chardet = None from ninix.home import get_normalized_path class MisakaError(ValueError): pass def lexical_error(path=None, position=None): if path is None and position is None: error = 'lexical error' elif path is None: at = 'line {0:d}, column {1:d}'.format(*position) error = 'lexical error at {0}'.format(at) elif position is None: error = 'lexical error in {0}'.format(path) else: at = 'line {0:d}, column {1:d}'.format(*position) error = 'lexical error at {0} in {1}'.format(at, path) raise MisakaError, error def syntax_error(message, path=None, position=None): if path is None and position is None: error = 'syntax error ({0})'.format(message) elif path is None: at = 'line {0:d}, column {1:d}'.format(*position) error = 'syntax error at {0} ({1})'.format(at, message) elif position is None: error = 'syntax error in {0} ({1})'.format(path, message) else: at = 'line {0:d}, column {1:d}'.format(*position) error = 'syntax error at {0} in {1} ({2})'.format(at, path, message) raise MisakaError, error def list_dict(top_dir): buf = [] try: filelist, debug, error = read_misaka_ini(top_dir) except Exception as e: filelist = [] for filename in filelist: buf.append(os.path.join(top_dir, filename)) return buf def read_misaka_ini(top_dir): path = os.path.join(top_dir, 'misaka.ini') with open(path) as f: filelist = [] debug = 0 error = 0 while 1: line = f.readline() if not line: break line = line.strip() if not line or line.startswith('//'): continue if line == 'dictionaries': line = f.readline() if line.strip() != '{': syntax_error('expected an open brace', path) while 1: line = f.readline() if not line: syntax_error('unexpected end of file', path) line = line.strip() if line == '}': break if not line or line.startswith('//'): continue filelist.append(get_normalized_path(line, encode=0)) elif line == 'debug,0': debug = 0 elif line == 'debug,1': debug = 1 elif line == 'error,0': error = 0 elif line == 'error,1': error = 1 elif line == 'propertyhandler,0': pass elif line == 'propertyhandler,1': pass else: logging.debug("misaka.py: unknown directive '{0}'".format(line)) return filelist, debug, error ### 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(object): re_comment = re.compile(r'[ \t]*//[^\r\n]*') re_newline = re.compile(r'\r\n|\r|\n') patterns = [ (TOKEN_NEWLINE, re_newline), (TOKEN_WHITESPACE, re.compile(r'[ \t]+')), (TOKEN_OPEN_BRACE, re.compile(r'{')), (TOKEN_CLOSE_BRACE, re.compile(r'}')), (TOKEN_OPEN_PAREN, re.compile(r'\(')), (TOKEN_CLOSE_PAREN, re.compile(r'\)')), (TOKEN_OPEN_BRACKET, re.compile(r'\[')), (TOKEN_CLOSE_BRACKET, re.compile(r'\]')), (TOKEN_DOLLAR, re.compile(r'\$')), (TOKEN_COMMA, re.compile(r',')), (TOKEN_SEMICOLON, re.compile(r';')), (TOKEN_OPERATOR, re.compile(r'[!=]=|[<>]=?|=[<>]|&&|\|\||\+\+|--|[+\-*/]?=|[+\-*/%\^]')), (TOKEN_DIRECTIVE, re.compile(r'#[_A-Za-z][_A-Za-z0-9]*')), #(TOKEN_TEXT, re.compile(r"[!&|]|(\\[,\"]|[\x01\x02#'.0-9:?@A-Z\\_`a-z~]|[\x80-\xff].)+")), (TOKEN_TEXT, re.compile(ur"(\"[^\"]*\")|[!&|]|(\\[,\"]|[\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 __init__(self, charset='Shift_JIS'): self.buffer = [] self.charset = charset def _match(self, data, line, column, path): pos = 0 end = len(data) while pos < end: if column == 0: match = self.re_comment.match(data, pos) if match: column = column + len(match.group()) pos = match.end() continue for token, pattern in self.patterns: match = pattern.match(data, pos) if match: lexeme = match.group() if token == TOKEN_TEXT and \ lexeme.startswith('"') and lexeme.endswith('"'): if '$' not in lexeme: self.buffer.append((token, lexeme[1:-1], (line, column))) else: # XXX line, column = self._match(lexeme[1:-1], line, column + 1, path) column += 1 else: self.buffer.append((token, lexeme, (line, column))) pos = match.end() break else: ###print data[pos:pos + 100] lexical_error(path, (line, column)) if token == TOKEN_NEWLINE: line = line + 1 column = 0 else: column = column + len(lexeme) return line, column def read(self, f, path=None): line = 1 column = 0 data = unicode(f.read(), self.charset, 'replace') line, column = self._match(data, line, column, path) self.buffer.append((TOKEN_NEWLINE, '', (line, column))) self.path = path def get_position(self): return self.position def pop(self): try: token, lexeme, self.position = self.buffer.pop(0) except IndexError: syntax_error('unexpected end of file', self.path) ###print token, repr(lexeme) return token, lexeme def pop_check(self, expected): token, lexeme = self.pop() if token != expected: syntax_error(''.join(('exptected ', self.token_names[expected])), self.path, self.get_position()) return lexeme def look_ahead(self, index=0): try: token, lexeme, position = self.buffer[index] except IndexError: syntax_error('unexpected end of file', self.path) return token, lexeme def skip_space(self, accept_eof=0): while self.buffer: token, lexeme = self.look_ahead() if token not in [TOKEN_NEWLINE, TOKEN_WHITESPACE]: return 0 self.pop() if not accept_eof: syntax_error('unexpected end of file', self.path) return 1 def skip_line(self): while self.buffer: token, lexeme = self.pop() if token == TOKEN_NEWLINE: break ### 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(object): def __init__(self, charset): self.lexer = Lexer(charset=charset) self.charset = charset def read(self, f, path=None): self.lexer.read(f, path) self.path = path def get_dict(self): common = None groups = [] # skip junk tokens at the beginning of the file while 1: if self.lexer.skip_space(1): return common, groups token, lexeme = self.lexer.look_ahead() if token == TOKEN_DIRECTIVE and lexeme == '#_Common' or \ token == TOKEN_DOLLAR: break self.lexer.skip_line() token, lexeme = self.lexer.look_ahead() if token == TOKEN_DIRECTIVE and lexeme == '#_Common': common = self.get_common() if self.lexer.skip_space(1): return common, groups while 1: groups.append(self.get_group()) if self.lexer.skip_space(1): return common, groups return None, [] def get_common(self): self.lexer.pop() token, lexeme = self.lexer.look_ahead() if token == TOKEN_WHITESPACE: self.lexer.pop() self.lexer.pop_check(TOKEN_NEWLINE) condition = self.get_brace_expr() self.lexer.pop_check(TOKEN_NEWLINE) return condition def get_group(self): # get group name buf = [] self.lexer.pop_check(TOKEN_DOLLAR) while 1: token, lexeme = self.lexer.look_ahead() ##if token == TOKEN_TEXT or \ ## token == TOKEN_OPERATOR and lexeme in ['+', '-', '*', '/', '%']: ## XXX if token not in [TOKEN_NEWLINE, TOKEN_COMMA, TOKEN_SEMICOLON]: token, lexeme = self.lexer.pop() buf.append(self.unescape(lexeme)) else: break if not buf: syntax_error('null identifier', self.path, self.lexer.get_position()) name = ''.join(('$', ''.join(buf))) # get group parameters parameters = [] while 1: token, lexeme = self.lexer.look_ahead() if token == TOKEN_WHITESPACE: self.lexer.pop() token, lexeme = self.lexer.look_ahead() if token == TOKEN_NEWLINE: break elif token in [TOKEN_COMMA, TOKEN_SEMICOLON]: self.lexer.pop() else: syntax_error('expected a delimiter', self.path, self.lexer.get_position()) token, lexeme = self.lexer.look_ahead() if token == TOKEN_WHITESPACE: self.lexer.pop() token, lexeme = self.lexer.look_ahead() if token == TOKEN_NEWLINE: break token, lexeme = self.lexer.look_ahead() if token == TOKEN_OPEN_BRACE: parameters.append(self.get_brace_expr()) elif token == TOKEN_TEXT: token, lexeme = self.lexer.pop() parameters.append([NODE_TEXT, self.unescape(lexeme)]) else: syntax_error('expected a parameter or brace expression', self.path, self.lexer.get_position()) # get sentences self.lexer.pop_check(TOKEN_NEWLINE) sentences = [] while 1: if self.lexer.skip_space(1): break token, lexeme = self.lexer.look_ahead() if token == TOKEN_DOLLAR: break sentence = self.get_sentence() if not sentence: continue sentences.append(sentence) return (name, parameters, sentences) def get_sentence(self): token, lexeme = self.lexer.look_ahead() if token == TOKEN_OPEN_BRACE: token, lexeme = self.lexer.look_ahead(1) if token == TOKEN_NEWLINE: self.lexer.pop_check(TOKEN_OPEN_BRACE) token, lexeme = self.lexer.look_ahead() if token == TOKEN_WHITESPACE: self.lexer.pop() self.lexer.pop_check(TOKEN_NEWLINE) line = [] while 1: self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if token == TOKEN_CLOSE_BRACE: break for node in self.get_line(): line.append(node) self.lexer.pop_check(TOKEN_NEWLINE) self.lexer.pop_check(TOKEN_CLOSE_BRACE) token, lexeme = self.lexer.look_ahead() if token == TOKEN_WHITESPACE: self.lexer.pop() else: try: line = self.get_line() # beginning with brace expression except MisakaError as error: logging.debug(error) self.lexer.skip_line() return None else: try: line = self.get_line() except MisakaError as error: logging.debug(error) self.lexer.skip_line() return None self.lexer.pop_check(TOKEN_NEWLINE) return line def is_whitespace(self, node): return node[0] == NODE_TEXT and not node[1].strip() def unescape(self, text): text = text.replace(r'\,', ',') text = text.replace(r'\"', '"') return text def get_word(self): buf = [] while 1: token, lexeme = self.lexer.look_ahead() if token == TOKEN_TEXT: token, lexeme = self.lexer.pop() if self.unescape(lexeme).startswith('"') and \ self.unescape(lexeme).endswith('"'): buf.append([NODE_STRING, self.unescape(lexeme)[1:-1]]) else: buf.append([NODE_TEXT, self.unescape(lexeme)]) elif token in [TOKEN_WHITESPACE, TOKEN_DOLLAR]: token, lexeme = self.lexer.pop() buf.append([NODE_TEXT, lexeme]) elif token == TOKEN_OPEN_BRACE: buf.append(self.get_brace_expr()) else: break # strip whitespace at the beginning and/or end of line if buf and self.is_whitespace(buf[0]): del buf[0] if buf and self.is_whitespace(buf[-1]): del buf[-1] return buf def get_line(self): buf = [] while 1: token, lexeme = self.lexer.look_ahead() if token in [TOKEN_NEWLINE, TOKEN_CLOSE_BRACE]: break elif token == TOKEN_TEXT: token, lexeme = self.lexer.pop() buf.append([NODE_TEXT, self.unescape(lexeme)]) elif token in [TOKEN_WHITESPACE, TOKEN_DOLLAR, TOKEN_OPEN_PAREN, TOKEN_CLOSE_PAREN, TOKEN_OPEN_BRACKET, TOKEN_CLOSE_BRACKET, TOKEN_OPERATOR, TOKEN_COMMA, TOKEN_SEMICOLON, TOKEN_DIRECTIVE]: token, lexeme = self.lexer.pop() buf.append([NODE_TEXT, lexeme]) elif token == TOKEN_OPEN_BRACE: buf.append(self.get_brace_expr()) else: raise RuntimeError, 'should not reach here' # strip whitespace at the beginning and/or end of line if buf and self.is_whitespace(buf[0]): del buf[0] if buf and self.is_whitespace(buf[-1]): del buf[-1] return buf def get_brace_expr(self): self.lexer.pop_check(TOKEN_OPEN_BRACE) self.lexer.skip_space() self.lexer.pop_check(TOKEN_DOLLAR) self.lexer.skip_space() # get identifier (function or variable) nodelist = [[NODE_TEXT, '$']] while 1: token, lexeme = self.lexer.look_ahead() if token == TOKEN_TEXT or \ token == TOKEN_OPERATOR and lexeme in ['+', '-', '*', '/', '%']: token, lexeme = self.lexer.pop() nodelist.append([NODE_TEXT, self.unescape(lexeme)]) elif token == TOKEN_OPEN_BRACE: nodelist.append(self.get_brace_expr()) else: break if len(nodelist) == 1: syntax_error('null identifier', self.path, self.lexer.get_position()) self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if token == TOKEN_OPEN_PAREN: # function name = ''.join([node[1] for node in nodelist]) if name == '$if': cond_expr = self.get_cond_expr() then_clause = [[NODE_TEXT, 'true']] else_clause = [[NODE_TEXT, 'false']] self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if token == TOKEN_OPEN_BRACE: self.lexer.pop_check(TOKEN_OPEN_BRACE) self.lexer.skip_space() then_clause = [] while 1: line = self.get_line() for node in line: then_clause.append(node) token, lexem = self.lexer.pop() if token == TOKEN_CLOSE_BRACE: break assert (token == TOKEN_NEWLINE) self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if token == TOKEN_TEXT and lexeme == 'else': self.lexer.pop() self.lexer.skip_space() self.lexer.pop_check(TOKEN_OPEN_BRACE) self.lexer.skip_space() else_clause = [] while 1: line = self.get_line() for node in line: else_clause.append(node) token, lexem = self.lexer.pop() if token == TOKEN_CLOSE_BRACE: break assert (token == TOKEN_NEWLINE) elif token == TOKEN_OPEN_BRACE: ## XXX self.lexer.pop_check(TOKEN_OPEN_BRACE) self.lexer.skip_space() else_clause = [] while 1: line = self.get_line() for node in line: else_clause.append(node) token, lexem = self.lexer.pop() if token == TOKEN_CLOSE_BRACE: break assert (token == TOKEN_NEWLINE) else: else_clause = [[NODE_TEXT, '']] node = [NODE_IF, cond_expr, then_clause, else_clause] elif name == '$calc': node = [NODE_CALC, self.get_add_expr()] else: self.lexer.pop_check(TOKEN_OPEN_PAREN) self.lexer.skip_space() args = [] token, lexeme = self.lexer.look_ahead() if token != TOKEN_CLOSE_PAREN: while 1: args.append(self.get_argument()) self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if token != TOKEN_COMMA: break self.lexer.pop() self.lexer.skip_space() self.lexer.pop_check(TOKEN_CLOSE_PAREN) node = [NODE_FUNCTION, name, args] else: # variable token, lexeme = self.lexer.look_ahead() if token == TOKEN_OPERATOR: operator = self.lexer.pop_check(TOKEN_OPERATOR) if operator in ['=', '+=', '-=', '*=', '/=']: self.lexer.skip_space() value = self.get_add_expr() elif operator == '++': operator = '+=' value = [[NODE_TEXT, '1']] elif operator == '--': operator = '-=' value = [[NODE_TEXT, '1']] else: syntax_error(''.join(('bad operator ', operator)), self.path, self.lexer.get_position()) node = [NODE_ASSIGNMENT, nodelist, operator, value] elif token == TOKEN_OPEN_BRACKET: self.lexer.pop_check(TOKEN_OPEN_BRACKET) self.lexer.skip_space() index = self.get_word() self.lexer.skip_space() self.lexer.pop_check(TOKEN_CLOSE_BRACKET) node = [NODE_INDEXED, nodelist, index] else: node = [NODE_VARIABLE, nodelist] self.lexer.skip_space() self.lexer.pop_check(TOKEN_CLOSE_BRACE) return node def get_argument(self): buf = [] while 1: token, lexeme = self.lexer.look_ahead() if token == TOKEN_TEXT: token, lexeme = self.lexer.pop() if self.unescape(lexeme).startswith('"') and \ self.unescape(lexeme).endswith('"'): buf.append([NODE_STRING, self.unescape(lexeme)[1:-1]]) else: buf.append([NODE_TEXT, self.unescape(lexeme)]) elif token in [TOKEN_WHITESPACE, TOKEN_DOLLAR, TOKEN_OPEN_BRACKET, TOKEN_CLOSE_BRACKET, TOKEN_OPERATOR, TOKEN_SEMICOLON]: token, lexeme = self.lexer.pop() buf.append([NODE_TEXT, lexeme]) elif token == TOKEN_OPEN_BRACE: buf.append(self.get_brace_expr()) elif token == TOKEN_NEWLINE: self.lexer.skip_space() else: break # strip whitespace at the beginning and/or end of line if buf and self.is_whitespace(buf[0]): del buf[0] if buf and self.is_whitespace(buf[-1]): del buf[-1] return buf def get_cond_expr(self): self.lexer.pop_check(TOKEN_OPEN_PAREN) self.lexer.skip_space() or_expr = self.get_or_expr() self.lexer.skip_space() self.lexer.pop_check(TOKEN_CLOSE_PAREN) return or_expr def get_or_expr(self): buf = [NODE_OR_EXPR] buf.append(self.get_and_expr()) while 1: self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if not (token == TOKEN_OPERATOR and lexeme == '||'): break self.lexer.pop() self.lexer.skip_space() buf.append(self.get_and_expr()) return buf if len(buf) > 2 else buf[1] def get_and_expr(self): buf = [NODE_AND_EXPR] buf.append(self.get_sub_expr()) while 1: self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if not (token == TOKEN_OPERATOR and lexeme == '&&'): break self.lexer.pop() self.lexer.skip_space() buf.append(self.get_sub_expr()) return buf if len(buf) > 2 else buf[1] def get_sub_expr(self): token, lexeme = self.lexer.look_ahead() if token == TOKEN_OPEN_PAREN: return self.get_cond_expr() else: return self.get_comp_expr() def get_comp_expr(self): buf = [NODE_COMP_EXPR] buf.append(self.get_add_expr()) self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if token == TOKEN_OPERATOR and \ lexeme in ['==', '!=', '<', '<=', '=<', '>', '>=', '=>']: if lexeme == '=<': lexeme = '<=' elif lexeme == '=>': lexeme = '>=' buf.append(lexeme) self.lexer.pop() self.lexer.skip_space() buf.append(self.get_add_expr()) if len(buf) == 2: buf.append('==') buf.append([[NODE_TEXT, 'true']]) return buf def get_add_expr(self): buf = [NODE_ADD_EXPR] buf.append(self.get_mul_expr()) while 1: self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if not (token == TOKEN_OPERATOR and lexeme in ['+', '-']): break buf.append(lexeme) self.lexer.pop() self.lexer.skip_space() buf.append(self.get_mul_expr()) return [buf] if len(buf) > 2 else buf[1] def get_mul_expr(self): buf = [NODE_MUL_EXPR] buf.append(self.get_pow_expr()) while 1: self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if not (token == TOKEN_OPERATOR and lexeme in ['*', '/', '%']): break buf.append(lexeme) self.lexer.pop() self.lexer.skip_space() buf.append(self.get_pow_expr()) return [buf] if len(buf) > 2 else buf[1] def get_pow_expr(self): buf = [NODE_POW_EXPR] buf.append(self.get_unary_expr()) while 1: self.lexer.skip_space() token, lexeme = self.lexer.look_ahead() if not (token == TOKEN_OPERATOR and lexeme == '^'): break self.lexer.pop() self.lexer.skip_space() buf.append(self.get_unary_expr()) return [buf] if len(buf) > 2 else buf[1] def get_unary_expr(self): token, lexeme = self.lexer.look_ahead() if token == TOKEN_OPERATOR and lexeme in ['+', '-']: buf = [NODE_ADD_EXPR, [[NODE_TEXT, '0']], lexeme] self.lexer.pop() self.lexer.skip_space() buf.append(self.get_unary_expr()) return [buf] else: return self.get_factor() def get_factor(self): token, lexeme = self.lexer.look_ahead() if token == TOKEN_OPEN_PAREN: self.lexer.pop_check(TOKEN_OPEN_PAREN) self.lexer.skip_space() add_expr = self.get_add_expr() self.lexer.skip_space() self.lexer.pop_check(TOKEN_CLOSE_PAREN) return add_expr else: return self.get_word() # for debug def dump_list(self, nodelist, depth=0): for node in nodelist: self.dump_node(node, depth) def dump_node(self, node, depth): indent = ' ' * depth if node[0] == NODE_TEXT: print ''.join((indent, 'TEXT')), \ ''.join(('"', node[1].encode('utf-8', 'ignore'), '"')) elif node[0] == NODE_STRING: print ''.join((indent, 'STRING')), \ ''.join(('"', node[1].encode('utf-8', 'ignore'), '"')) elif node[0] == NODE_VARIABLE: print ''.join((indent, 'VARIABLE')) self.dump_list(node[1], depth + 1) elif node[0] == NODE_INDEXED: print ''.join((indent, 'INDEXED')) self.dump_list(node[1], depth + 1) print ''.join((indent, 'index')) self.dump_list(node[2], depth + 1) elif node[0] == NODE_ASSIGNMENT: print ''.join((indent, 'ASSIGNMENT')) self.dump_list(node[1], depth + 1) print ''.join((indent, 'op')), node[2] self.dump_list(node[3], depth + 1) elif node[0] == NODE_FUNCTION: print ''.join((indent, 'FUNCTION')), node[1] for i in range(len(node[2])): print ''.join((indent, 'args[{0:d}]'.format(i))) self.dump_list(node[2][i], depth + 1) elif node[0] == NODE_IF: print ''.join((indent, 'IF')) self.dump_node(node[1], depth + 1) print ''.join((indent, 'then')) self.dump_list(node[2], depth + 1) print ''.join((indent, 'else')) self.dump_list(node[3], depth + 1) elif node[0] == NODE_CALC: print ''.join((indent, 'CALC')) self.dump_list(node[1], depth + 1) elif node[0] == NODE_AND_EXPR: print ''.join((indent, 'AND_EXPR')) for i in range(1, len(node)): self.dump_node(node[i], depth + 1) elif node[0] == NODE_OR_EXPR: print ''.join((indent, 'OR_EXPR')) for i in range(1, len(node)): self.dump_node(node[i], depth + 1) elif node[0] == NODE_COMP_EXPR: print ''.join((indent, 'COMP_EXPR')) self.dump_list(node[1], depth + 1) print ''.join((indent, 'op')), node[2] self.dump_list(node[3], depth + 1) elif node[0] == NODE_ADD_EXPR: print ''.join((indent, 'ADD_EXPR')) self.dump_list(node[1], depth + 1) for i in range(2, len(node), 2): print ''.join((indent, 'op')), node[i] self.dump_list(node[i + 1], depth + 1) elif node[0] == NODE_MUL_EXPR: print ''.join((indent, 'MUL_EXPR')) self.dump_list(node[1], depth + 1) for i in range(2, len(node), 2): print ''.join((indent, 'op')), node[i] self.dump_list(node[i + 1], depth + 1) elif node[0] == NODE_POW_EXPR: print ''.join((indent, 'POW_EXPR')) self.dump_list(node[1], depth + 1) for i in range(2, len(node)): print ''.join((indent, 'op ^')) self.dump_list(node[i], depth + 1) else: print node raise RuntimeError, 'should not reach here' # <<< 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(object): def __init__(self, misaka, name, item_list=None): self.misaka = misaka self.name = name self.list = item_list or [] def __len__(self): return len(self.list) def __getitem__(self, index): return self.list[index] def get(self): if not self.list: return None return random.choice(self.list) def copy(self, name): return Array(self.misaka, name, self.list[:]) class NonOverlapGroup(Group): def __init__(self, misaka, name, item_list=None): Group.__init__(self, misaka, name, item_list) self.indexes = [] def get(self): if not self.list: return None if not self.indexes: self.indexes = list(range(len(self.list))) index = self.indexes.pop(random.choice(range(len(self.indexes)))) return self.list[index] class SequentialGroup(Group): def __init__(self, misaka, name, item_list=None): Group.__init__(self, misaka, name, item_list) self.indexes = [] def get(self): if not self.list: return None if not self.indexes: self.indexes = list(range(len(self.list))) index = self.indexes.pop(0) return self.list[index] class Array(Group): def append(self, s): self.list.append([[NODE_TEXT, unicode(s)]]) def index(self, s): for i in range(len(self.list)): if s == self.misaka.expand(self.list[i]): return i return -1 def pop(self): if not self.list: return None return self.list.pop(random.choice(range(len(self.list)))) def popmatchl(self, s): buf = [] for i in range(len(self.list)): t = self.misaka.expand(self.list[i]) if t.startswith(s): buf.append(i) if not buf: return None return self.list.pop(random.choice(buf)) TYPE_SCHOLAR = 1 TYPE_ARRAY = 2 class Shiori(object): DBNAME = 'misaka.db' def __init__(self, dll_name): self.dll_name = dll_name self.charset = 'Shift_JIS' def use_saori(self, saori): self.saori = saori def find(self, top_dir, dll_name): result = 0 if list_dict(top_dir): result = 210 elif os.path.isfile(os.path.join(top_dir, 'misaka.ini')): result = 200 return result def show_description(self): 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-2012 by Shyouzou Sugitani') def reset(self): self.dict = {} self.variable = {} self.constant = {} self.misaka_debug = 0 self.misaka_error = 0 self.reference = (None, None, None, None, None, None, None, None) self.mouse_move_count = {} self.random_talk = -1 self.otherghost = [] self.communicate = ['', ''] self.reset_request() def load(self, misaka_dir=os.curdir): self.misaka_dir = misaka_dir self.dbpath = os.path.join(self.misaka_dir, self.DBNAME) self.saori_library = SaoriLibrary(self.saori, self.misaka_dir) self.reset() try: filelist, debug, error = read_misaka_ini(self.misaka_dir) except IOError: logging.debug('cannot read misaka.ini') return 0 except MisakaError as error: logging.debug(error) return 0 self.misaka_debug = debug self.misaka_error = error global_variables = [] global_constants = [] # charset auto-detection if chardet is not None: detector = chardet.universaldetector.UniversalDetector() for filename in filelist: path = os.path.join(self.misaka_dir, filename) try: f = open(path) except IOError: logging.debug('cannot read {0}'.format(filename)) continue basename, ext = os.path.splitext(filename) if ext == '.__1': f = StringIO.StringIO(self.crypt(f.read())) detector.reset() for line in f: detector.feed(line) if detector.done: break detector.close() if detector.result['confidence'] > 0.98: # XXX self.charset = detector.result['encoding'] break for filename in filelist: path = os.path.join(self.misaka_dir, filename) try: f = open(path) except IOError: logging.debug('cannot read {0}'.format(filename)) continue basename, ext = os.path.splitext(filename) if ext == '.__1': f = StringIO.StringIO(self.crypt(f.read())) try: variables, constants = self.read(f, path) except MisakaError as error: logging.debug(error) continue global_variables.extend(variables) global_constants.extend(constants) self.eval_globals(global_variables, 0) self.load_database() self.eval_globals(global_constants, 1) if '$_OnRandomTalk' in self.dict: self.random_talk = 0 return 1 def crypt(self, data): return ''.join([chr(ord(c) ^ 0xff) for c in data]) def eval_globals(self, sentences, constant): for sentence in sentences: for node in sentence: if node[0] == NODE_ASSIGNMENT: self.eval_assignment(node[1], node[2], node[3], constant) elif node[0] == NODE_FUNCTION: self.eval_function(node[1], node[2]) def read(self, f, path=None): parser = Parser(self.charset) parser.read(f, path) common, dic = parser.get_dict() variables = [] constants = [] for name, parameters, sentences in dic: if not sentences: continue elif name == '$_Variable': variables.extend(sentences) continue elif name == '$_Constant': constants.extend(sentences) continue GroupClass = Group conditions = [] if common is not None: conditions.append(common) for node in parameters: if node[0] == NODE_IF: conditions.append(node) elif node[0] == NODE_TEXT and node[1] == 'nonoverlap': GroupClass = NonOverlapGroup elif node[0] == NODE_TEXT and node[1] == 'sequential': GroupClass = SequentialGroup else: pass # ignore unknown parameters group = GroupClass(self, name, sentences) try: grouplist = self.dict[name] except KeyError: grouplist = self.dict[name] = [] grouplist.append((group, conditions)) return variables, constants def strip_newline(self, line): if line.endswith('\r\n'): return line[:-2] elif line.endswith('\r') or line.endswith('\n'): return line[:-1] return line def load_database(self): try: with open(self.dbpath) as f: while 1: line = f.readline() if not line: break header = self.strip_newline(line).split() if header[0] == 'SCHOLAR': try: name = unicode(header[1], 'utf-8') except UnicodeError: try: name = unicode(header[1], 'EUC-JP') except UnicodeError: logging.debug( 'misaka.py: malformed database (ignored)') break value = self.strip_newline(f.readline()) try: value = unicode(value, 'utf-8') except UnicodeError: try: value = unicode(value, 'EUC-JP') except UnicodeError: logging.debug( 'misaka.py: malformed database (ignored)') break self.variable[name] = (TYPE_SCHOLAR, value) elif header[0] == 'ARRAY': try: size = int(header[1]) except ValueError: logging.debug( 'misaka.py: malformed database (ignored)') break try: name = unicode(header[2], 'utf-8') except UnicodeError: try: name = unicode(header[2], 'EUC-JP') except UnicodeError: logging.debug( 'misaka.py: malformed database (ignored)') break array = Array(self, name) for _ in range(size): value = self.strip_newline(f.readline()) try: value = unicode(value, 'utf-8') except UnicodeError: try: value = unicode(value, 'EUC-JP') except UnicodeError: logging.debug( 'misaka.py: malformed database (ignored)') break array.append(value) self.variable[name] = (TYPE_ARRAY, array) else: raise RuntimeError, 'should not reach here' except IOError: return def save_database(self): try: with open(self.dbpath, 'w') as f: for name in self.variable: value_type, value = self.variable[name] if value_type == TYPE_SCHOLAR: if value == '': continue f.write( 'SCHOLAR {0}\n{1}\n'.format( name.encode('utf-8'), unicode(value).encode('utf-8'))) elif value_type == TYPE_ARRAY: f.write( 'ARRAY {0:d} {1}\n'.format(len(value), name.encode('utf-8'))) for item in value: f.write('{0}\n'.format(self.expand(item).encode('utf-8'))) else: raise RuntimeError, 'should not reach here' except IOError: logging.debug('misaka.py: cannot write database (ignored)') return def unload(self): self.save_database() self.saori_library.unload() return 1 def reset_request(self): self.req_command = '' self.req_protocol = '' self.req_key = [] self.req_header = {} def request(self, req_string): header = req_string.splitlines() line = header.pop(0) if line: line = line.strip() req_list = line.split() if len(req_list) >= 2: self.req_command = req_list[0].strip() self.req_protocol = req_list[1].strip() for line in header: line = line.strip() if not line: continue if ':' not in line: continue key, value = line.split(':', 1) key = key.strip() try: value = int(value.strip()) except: value = unicode(str(value), self.charset, 'ignore').strip() self.req_key.append(key) self.req_header[key] = value # FIXME ref0 = self.get_ref(0) ref1 = self.get_ref(1) ref2 = self.get_ref(2) ref3 = self.get_ref(3) ref4 = self.get_ref(4) ref5 = self.get_ref(5) ref6 = self.get_ref(6) ref7 = self.get_ref(7) event = self.req_header['ID'] script = None if event == 'otherghostname': n = 0 refs = [] while 1: ref = self.get_ref(n) if ref: refs.append(ref) else: break n += 1 self.otherghost = [] for ref in refs: name, s0, s1 = ref.split(chr(1)) self.otherghost.append([name, s0, s1]) if event == 'OnMouseMove': key = (ref3, ref4) # side, part count = self.mouse_move_count.get(key, 0) self.mouse_move_count[key] = count + 5 if event == 'OnCommunicate': self.communicate = [ref0, ref1] script = self.get('$_OnGhostCommunicateReceive', default=None) elif event == 'charset': script = self.charset else: self.reference = (ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7) script = self.get(''.join(('$', event)), default=None) if event == 'OnSecondChange': if self.random_talk == 0: self.reset_random_talk_interval() elif self.random_talk > 0: self.random_talk -= 1 if self.random_talk == 0: script = self.get('$_OnRandomTalk', default=None) if script is not None: script = script.strip() else: script = '' self.reset_request() response = 'SHIORI/3.0 200 OK\r\n' \ 'Sender: Misaka\r\n' \ 'Charset: {0}\r\n' \ 'Value: {1}\r\n'.format(self.charset, script.encode(self.charset, 'ignore')) to = self.eval_variable([[NODE_TEXT, '$to']]) if to: del self.variable['$to'] response = ''.join((response, 'Reference0: {0}\r\n'.format( to.encode(self.charset, 'ignore')))) response = ''.join((response, '\r\n')) return response def get_ref(self, num): key = ''.join(('Reference', str(num))) return self.req_header.get(key) # internal def reset_random_talk_interval(self): interval = 0 value_type, value = self.variable.get('$_talkinterval', (TYPE_SCHOLAR, '')) if value_type == TYPE_SCHOLAR: try: interval = max(int(value), 0) except ValueError: pass self.random_talk = int(interval * random.randint(5, 15) / 10.0) def get(self, name, default=''): grouplist = self.dict.get(name) if grouplist is None: return default for group, conditions in grouplist: if self.eval_and_expr(conditions) == 'true': return self.expand(group.get()) return default def expand(self, nodelist): if nodelist is None: return '' buf = [] for node in nodelist: buf.append(self.eval(node)) return ''.join(buf) def expand_args(self, nodelist): tmp = self.expand(nodelist) return self.eval_variable(nodelist) if tmp.startswith('$') else tmp def eval(self, node): if node[0] == NODE_TEXT: result = node[1] elif node[0] == NODE_STRING: result = node[1] elif node[0] == NODE_VARIABLE: result = self.eval_variable(node[1]) elif node[0] == NODE_INDEXED: result = self.eval_indexed(node[1], node[2]) elif node[0] == NODE_ASSIGNMENT: result = self.eval_assignment(node[1], node[2], node[3]) elif node[0] == NODE_FUNCTION: result = self.eval_function(node[1], node[2]) elif node[0] == NODE_IF: result = self.eval_if(node[1], node[2], node[3]) elif node[0] == NODE_CALC: result = self.eval_calc(node[1]) elif node[0] == NODE_COMP_EXPR: result = self.eval_comp_expr(node[1], node[2], node[3]) elif node[0] == NODE_AND_EXPR: result = self.eval_and_expr(node[1:]) elif node[0] == NODE_OR_EXPR: result = self.eval_or_expr(node[1:]) elif node[0] == NODE_ADD_EXPR: result = self.eval_add_expr(node[1:]) elif node[0] == NODE_MUL_EXPR: result = self.eval_mul_expr(node[1:]) elif node[0] == NODE_POW_EXPR: result = self.eval_pow_expr(node[1:]) else: ##print node raise RuntimeError, 'should not reach here' return result 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(self, name): now = time.localtime(time.time()) name = self.expand(name) if name == '$year': result = str(now[0]) elif name == '$month': result = str(now[1]) elif name == '$day': result = str(now[2]) elif name == '$hour': result = str(now[3]) elif name == '$minute': result = str(now[4]) elif name == '$second': result = str(now[5]) elif name == '$dayofweek': result = str([1, 2, 3, 4, 5, 6, 0][now[6]]) elif name == '$lastsentence': result = self.communicate[1] elif name == '$otherghostlist': if self.otherghost: ghost = random.choice(self.otherghost) result = ghost[0] else: result = '' elif name == '$sender': result = self.communicate[0] elif name in self.SYSTEM_VARIABLES: result = '' elif name in self.dict: result = self.get(name) elif name in self.constant: value_type, value = self.constant[name] if value_type == TYPE_ARRAY: result = self.expand(value.get()) else: result = unicode(value) elif name in self.variable: value_type, value = self.variable[name] if value_type == TYPE_ARRAY: result = self.expand(value.get()) else: result = unicode(value) else: result = '' return result def eval_indexed(self, name, index): name = self.expand(name) index = self.expand(index) try: index = int(index) except ValueError: index = 0 if name == '$otherghostlist': group = [] for ghost in self.otherghost: group.append(ghost[0]) elif name in self.SYSTEM_VARIABLES: return '' elif name in self.dict: grouplist = self.dict[name] group, constraints = grouplist[0] elif name in self.constant: return '' elif name in self.variable: value_type, group = self.variable[name] if value_type != TYPE_ARRAY: return '' else: return '' if index < 0 or index >= len(group): return '' return self.expand(group[index]) def eval_assignment(self, name, operator, value, constant=0): name = self.expand(name) value = self.expand(value) if operator in ['+=', '-=', '*=', '/=']: try: operand = int(value) except ValueError: operand = 0 value_type, value = self.constant.get(name, (None, '')) if value_type is None: value_type, value = self.variable.get(name, (TYPE_SCHOLAR, '')) if value_type == TYPE_ARRAY: return '' try: value = int(value) except ValueError: value = 0 if operator == '+=': value += operand elif operator == '-=': value -= operand elif operator == '*=': value *= operand elif operator == '/=' and operand != 0: value /= operand if constant or name in self.constant: self.constant[name] = (TYPE_SCHOLAR, value) else: self.variable[name] = (TYPE_SCHOLAR, value) if name == '$_talkinterval': self.reset_random_talk_interval() return '' def eval_comp_expr(self, operand1, operator, operand2): value1 = self.expand(operand1) value2 = self.expand(operand2) try: operand1 = int(value1) operand2 = int(value2) except ValueError: operand1 = value1 operand2 = value2 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' return 'false' def eval_and_expr(self, conditions): for condition in conditions: boolean = self.eval(condition) if boolean.strip() != 'true': return 'false' return 'true' def eval_or_expr(self, conditions): for condition in conditions: boolean = self.eval(condition) if boolean.strip() == 'true': return 'true' return 'false' def eval_add_expr(self, expression): value = self.expand(expression[0]) try: value = int(value) except ValueError: value = 0 for i in range(1, len(expression), 2): operand = self.expand(expression[i + 1]) try: operand = int(operand) except ValueError: operand = 0 if expression[i] == '+': value += operand elif expression[i] == '-': value -= operand return str(value) def eval_mul_expr(self, expression): value = self.expand(expression[0]) try: value = int(value) except ValueError: value = 0 for i in range(1, len(expression), 2): operand = self.expand(expression[i + 1]) try: operand = int(operand) except ValueError: operand = 0 if expression[i] == '*': value *= operand elif expression[i] == '/' and operand != 0: value /= operand elif expression[i] == '%' and operand != 0: value %= operand return str(value) def eval_pow_expr(self, expression): value = self.expand(expression[-1]) try: value = int(value) except ValueError: value = 0 for i in range(1, len(expression)): operand = self.expand(expression[-i - 1]) try: operand = int(operand) except ValueError: operand = 0 value = operand**value return str(value) def eval_if(self, condition, then_clause, else_clause): boolean = self.eval(condition) if boolean.strip() == 'true': return self.expand(then_clause) else: return self.expand(else_clause) def eval_calc(self, expression): return self.expand(expression) def eval_function(self, name, args): function = self.SYSTEM_FUNCTIONS.get(name) if function is None: return '' return function(self, args) def is_number(self, s): return s and all([bool(c in '0123456789') for c in s]) def split(self, s): buf = [] i, j = 0, len(s) while i < j: if ord(s[i]) < 0x80: buf.append(s[i]) i += 1 else: buf.append(s[i:i + 2]) i += 2 return buf def exec_reference(self, args): if len(args) != 1: return '' n = self.expand_args(args[0]) if n in '01234567': value = self.reference[int(n)] if value is not None: return unicode(value) return '' def exec_random(self, args): if len(args) != 1: return '' n = self.expand_args(args[0]) try: return str(random.randrange(int(n))) except ValueError: return '' # n < 1 or not a number def exec_choice(self, args): if not args: return '' return self.expand_args(random.choice(args)) def exec_getvalue(self, args): if len(args) != 2: return '' try: n = int(self.expand_args(args[1])) except ValueError: return '' if n < 0: return '' value_list = self.expand_args(args[0]).split(',') try: return value_list[n] except IndexError: return '' def exec_search(self, args): namelist = [] for arg in args: name = self.expand_args(arg) if name.strip(): namelist.append(name) if not namelist: return '' keylist = [] for key in self.keys(): # dict, variable, constant for name in namelist: if name not in key: break else: keylist.append(key) if not keylist: return '' return self.eval_variable([[NODE_TEXT, random.choice(keylist)]]) def keys(self): buf = list(self.dict.keys()) buf.extend(self.variable.keys()) buf.extend(self.constant.keys()) return buf def exec_backup(self, args): if not args: self.save_database() return '' def exec_getmousemovecount(self, args): if len(args) == 2: side = self.expand_args(args[0]) part = self.expand_args(args[1]) try: key = (int(side), part) except ValueError: pass else: return str(self.mouse_move_count.get(key, 0)) return '' def exec_resetmousemovecount(self, args): if len(args) == 2: side = self.expand_args(args[0]) part = self.expand_args(args[1]) try: key = (int(side), part) except ValueError: pass else: self.mouse_move_count[key] = 0 return '' def exec_substring(self, args): if len(args) != 3: return '' value = self.expand_args(args[2]) try: count = int(value) except ValueError: return '' if count < 0: return '' value = self.expand_args(args[1]) try: offset = int(value) except ValueError: return '' if offset < 0: return '' s = self.expand_args(args[0]).encode(self.charset, 'replace') return unicode(s[offset:offset + count], self.charset, 'replace') def exec_substringl(self, args): if len(args) != 2: return '' value = self.expand_args(args[1]) try: count = int(value) except ValueError: return '' if count < 0: return '' s = self.expand_args(args[0]).encode(self.charset, 'replace') return unicode(s[:count], self.charset, 'replace') def exec_substringr(self, args): if len(args) != 2: return '' value = self.expand_args(args[1]) try: count = int(value) except ValueError: return '' if count < 0: return '' s = self.expand_args(args[0]).encode(self.charset, 'replace') return unicode(s[len(s) - count:], self.charset, 'replace') def exec_substringfirst(self, args): if len(args) != 1: return '' buf = self.expand_args(args[0]) return '' if not buf else buf[0] def exec_substringlast(self, args): if len(args) != 1: return '' buf = self.expand_args(args[0]) return '' if not buf else buf[-1] def exec_length(self, args): if len(args) != 1: return '' return str( len(self.expand_args(args[0]).encode(self.charset, 'replace'))) katakana2hiragana = { 'ァ': 'ぁ', 'ア': 'あ', 'ィ': 'ぃ', 'イ': 'い', 'ゥ': 'ぅ', 'ウ': 'う', 'ェ': 'ぇ', 'エ': 'え', 'ォ': 'ぉ', 'オ': 'お', 'カ': 'か', 'ガ': 'が', 'キ': 'き', 'ギ': 'ぎ', 'ク': 'く', 'グ': 'ぐ', 'ケ': 'け', 'ゲ': 'げ', 'コ': 'こ', 'ゴ': 'ご', 'サ': 'さ', 'ザ': 'ざ', 'シ': 'し', 'ジ': 'じ', 'ス': 'す', 'ズ': 'ず', 'セ': 'せ', 'ゼ': 'ぜ', 'ソ': 'そ', 'ゾ': 'ぞ', 'タ': 'た', 'ダ': 'だ', 'チ': 'ち', 'ヂ': 'ぢ', 'ッ': 'っ', 'ツ': 'つ', 'ヅ': 'づ', 'テ': 'て', 'デ': 'で', 'ト': 'と', 'ド': 'ど', 'ナ': 'な', 'ニ': 'に', 'ヌ': 'ぬ', 'ネ': 'ね', 'ノ': 'の', 'ハ': 'は', 'バ': 'ば', 'パ': 'ぱ', 'ヒ': 'ひ', 'ビ': 'び', 'ピ': 'ぴ', 'フ': 'ふ', 'ブ': 'ぶ', 'プ': 'ぷ', 'ヘ': 'へ', 'ベ': 'べ', 'ペ': 'ぺ', 'ホ': 'ほ', 'ボ': 'ぼ', 'ポ': 'ぽ', 'マ': 'ま', 'ミ': 'み', 'ム': 'む', 'メ': 'め', 'モ': 'も', 'ャ': 'ゃ', 'ヤ': 'や', 'ュ': 'ゅ', 'ユ': 'ゆ', 'ョ': 'ょ', 'ヨ': 'よ', 'ラ': 'ら', 'リ': 'り', 'ル': 'る', 'レ': 'れ', 'ロ': 'ろ', 'ヮ': 'ゎ', 'ワ': 'わ', 'ヰ': 'ゐ', 'ヱ': 'ゑ', 'ヲ': 'を', 'ン': 'ん', 'ヴ': 'う゛', } def exec_hiraganacase(self, args): if len(args) != 1: return '' buf = [] string = unicode(self.expand_args(args[0]), self.charset, 'replace') #string = unicode(args[0], self.charset, 'replace') for c in string: c = c.encode('utf-8') c = self.katakana2hiragana.get(c, c) c = unicode(c, 'utf-8') buf.append(c) return ''.join(buf) def exec_isequallastandfirst(self, args): if len(args) != 2: return 'false' buf0 = self.expand_args(args[0]) buf1 = self.expand_args(args[1]) return 'true' if buf0 and buf1 and buf0[-1] == buf1[0] else 'false' def exec_append(self, args): if len(args) == 2: name = self.expand_args(args[0]) if name and name.startswith('$'): value_type, value = self.variable.get(name, (TYPE_SCHOLAR, '')) if value_type == TYPE_SCHOLAR: value_type = TYPE_ARRAY array = Array(self, name) if value != '': array.append(value) value = array member_list = self.expand_args(args[1]).split(chr(1)) for member in member_list: value.append(member) self.variable[name] = (value_type, value) return '' def exec_stringexists(self, args): if len(args) != 2: return '' name = self.expand_args(args[0]) value_type, value = self.variable.get(name, (TYPE_SCHOLAR, '')) if value_type == TYPE_SCHOLAR: return '' if value.index(self.expand_args(args[1])) < 0: return 'false' else: return 'true' def exec_copy(self, args): if len(args) != 2: return '' src_name = self.expand_args(args[0]) if not (src_name and src_name.startswith('$')): return '' new_name = self.expand_args(args[1]) if not (new_name and new_name.startswith('$')): return '' source = None if src_name in self.dict: grouplist = self.dict[src_name] source, conditions = grouplist[0] elif src_name in self.variable: value_type, value = self.variable.get(src_name) if value_type == TYPE_ARRAY: source = value if source is None: value = Array(self, new_name) else: value = source.copy(new_name) self.variable[new_name] = (TYPE_ARRAY, value) return '' def exec_pop(self, args): if len(args) == 1: name = self.expand_args(args[0]) value_type, value = self.variable.get(name, (TYPE_SCHOLAR, '')) if value_type == TYPE_ARRAY: return self.expand(value.pop()) return '' def exec_popmatchl(self, args): if len(args) == 2: name = self.expand_args(args[0]) value_type, value = self.variable.get(name, (TYPE_SCHOLAR, '')) if value_type == TYPE_ARRAY: return self.expand(value.popmatchl(self.expand_args(args[1]))) return '' def exec_index(self, args): if len(args) == 2: pos = self.expand_args(args[1]).find(self.expand_args(args[0])) if pos > 0: s = self.expand_args(args[0])[pos] pos = len(s.encode(self.charset, 'replace')) return str(pos) return '' def exec_insentence(self, args): if not args: return '' s = self.expand_args(args[0]) for i in range(1, len(args)): if self.expand_args(args[i]) not in s: return 'false' return 'true' def exec_substringw(self, args): if len(args) != 3: return '' value = self.expand_args(args[2]) try: count = int(value) except ValueError: return '' if count < 0: return '' value = self.expand_args(args[1]) try: offset = int(value) except ValueError: return '' if offset < 0: return '' buf = self.expand_args(args[0]) return buf[offset:offset + count] def exec_substringwl(self, args): if len(args) != 2: return '' value = self.expand_args(args[1]) try: count = int(value) except ValueError: return '' if count < 0: return '' buf = self.expand_args(args[0]) return buf[:count] def exec_substringwr(self, args): if len(args) != 2: return '' value = self.expand_args(args[1]) try: count = int(value) except ValueError: return '' if count < 0: return '' buf = self.expand_args(args[0]) return buf[len(buf) - count:] prefixes = ['さん', 'ちゃん', '君', 'くん', '様', 'さま', '氏', '先生'] def exec_adjustprefix(self, args): if len(args) != 2: return '' s = self.expand_args(args[0]) for prefix in self.prefixes: prefix = unicode(prefix, 'utf-8') if s.endswith(prefix): s = ''.join((s[:-len(prefix)], self.expand_args(args[1]))) break return s def exec_count(self, args): if len(args) != 1: return '' name = self.expand_args(args[0]) try: value_type, value = self.variable[name] if value_type == TYPE_SCHOLAR: return '1' return str(len(value)) except KeyError: return '-1' def exec_inlastsentence(self, args): if not args: return '' s = self.communicate[1] for i in range(1, len(args)): if self.expand_args(args[i]) not in s: return 'false' return 'true' def saori(self, 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: {0}\r\n'.format(self.charset) for i in range(1, len(args)): req = ''.join( (req, 'Argument{0}: {1}\r\n'.format( i, self.expand_args(args[i]).encode(self.charset, 'ignore')))) req = ''.join((req, '\r\n')) response = self.saori_library.request(self.expand_args(args[0]), req) header = response.splitlines() line = header.pop(0) if line: line = line.strip() if ' ' in line: saori_protocol, saori_statuscode = [x.strip for x in line.split(' ', 1)] for line in header: line = line.strip() if not line: continue if ':' not in line: continue key, value = [x.strip() for x in line.split(':', 1)] if key: saori_header.append(key) saori_value[key] = value return saori_value.get('Result', '') def load_saori(self, args): if not args: return '' result = self.saori_library.load(self.expand_args(args[0]), self.misaka_dir) return '' if not result else str(result) def unload_saori(self, args): if not args: return '' result = self.saori_library.unload(self.expand_args(args[0])) return '' if not result else str(result) def exec_isghostexists(self, args): result = 'false' if len(args) != 1: if len(self.otherghost): result = 'true' self.variable['$to'] = (TYPE_SCHOLAR, random.choice(self.otherghost)[0]) else: for ghost in self.otherghost: if ghost[0] == self.expand_args(args[0]): result = 'true' break return result 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(object): def __init__(self, saori, top_dir): self.saori_list = {} self.saori = saori def load(self, name, top_dir): result = 0 name = os.path.split(name.replace('\\', '/'))[-1] # XXX if name not in self.saori_list: module = self.saori.request(name) if module: self.saori_list[name] = module if name in self.saori_list: result = self.saori_list[name].load(top_dir) return result def unload(self, name=None): if name: name = os.path.split(name.replace('\\', '/'))[-1] # XXX if name in self.saori_list: self.saori_list[name].unload() del self.saori_list[name] else: for saori in self.saori_list.values(): saori.unload() return None def request(self, name, req): result = '' # FIXME name = os.path.split(name.replace('\\', '/'))[-1] # XXX if name and name in self.saori_list: result = self.saori_list[name].request(req) return result def misaka_open(top_dir): misaka = Shiori(top_dir) misaka.load() return misaka ### TEST ### def test_ini(dirlist): for top_dir in dirlist: print 'Reading', os.path.join(top_dir, 'misaka.ini'), '...' filelist, debug, error = read_misaka_ini(top_dir) print 'number of dictionaries =', len(filelist) for filename in filelist: print filename print 'debug =', debug print 'error =', error def test_lexer(filelist, charset='Shift_JIS'): lexer = Lexer(charset=charset) for filename in filelist: print 'Reading', filename, '...' lexer.read(open(filename)) token_names = { TOKEN_WHITESPACE: 'TOKEN_WHITESPACE', TOKEN_NEWLINE: 'TOKEN_NEWLINE', TOKEN_OPEN_BRACE: 'TOKEN_OPEN_BRACE', TOKEN_CLOSE_BRACE: 'TOKEN_CLOSE_BRACE', TOKEN_OPEN_PAREN: 'TOKEN_OPEN_PAREN', TOKEN_CLOSE_PAREN: 'TOKEN_CLOSE_PAREN', TOKEN_OPEN_BRACKET: 'TOKEN_OPEN_BRACKET', TOKEN_CLOSE_BRACKET: 'TOKEN_CLOSE_BRACKET', TOKEN_DOLLAR: 'TOKEN_DOLLAR', TOKEN_COMMA: 'TOKEN_COMMA', TOKEN_SEMICOLON: 'TOKEN_SEMICOLON', TOKEN_OPERATOR: 'TOKEN_OPERATOR', TOKEN_DIRECTIVE: 'TOKEN_DIRECTIVE', TOKEN_TEXT: 'TOKEN_TEXT', } for token, lexeme, position in lexer.buffer: print 'L{0:d} : C{1:-3d} :'.format(*position), token_names[token], ':', if token in [TOKEN_WHITESPACE, TOKEN_NEWLINE, TOKEN_OPEN_BRACE, TOKEN_CLOSE_BRACE, TOKEN_OPEN_PAREN, TOKEN_CLOSE_PAREN, TOKEN_OPEN_BRACKET, TOKEN_CLOSE_BRACKET, TOKEN_DOLLAR, TOKEN_COMMA, TOKEN_SEMICOLON]: print repr(lexeme) else: print lexeme.encode('utf-8', 'ignore') def test_parser(filelist, charset='Shift_JIS'): for filename in filelist: print 'Reading', filename, '...' parser = Parser(charset=charset) parser.read(open(filename)) common, dic = parser.get_dict() if common is not None: print 'Common' parser.dump_node(common, depth=2) for name, parameters, sentences in dic: print 'Group', name.encode('utf-8', 'ignore') if parameters: print 'Parameter:' for parameter in parameters: print '>>>', parameter parser.dump_node(parameter, depth=2) if sentences: print 'Sentence:' for sentence in sentences: print '>>>', sentence parser.dump_list(sentence, depth=2) def test_interpreter(top_dir): misaka = Shiori(top_dir) misaka.load() while 1: try: line = raw_input('>>> ') except KeyboardInterrupt: print 'Break' continue except EOFError: print break command = line.split() if not command: continue elif len(command) == 1 and command[0].startswith('$'): print misaka.eval_variable([[NODE_TEXT, command[0]]]) elif len(command) == 1 and command[0] == 'reload': misaka.load() else: print 'list of commands:' print ' $id get variable value' print ' reload reload dictionaries' if __name__ == '__main__': logger = logging.getLogger() logger.setLevel(logging.DEBUG) # XXX USAGE= 'Usage: misaka.py [ini|lexer|parser|interp] ...' charset = 'Shift_JIS' # XXX if len(sys.argv) == 1: print USAGE elif sys.argv[1] == 'ini': test_ini(sys.argv[2:]) elif sys.argv[1] == 'lexer': test_lexer(sys.argv[2:], charset) elif sys.argv[1] == 'parser': test_parser(sys.argv[2:], charset) elif sys.argv[1] == 'interp': test_interpreter(sys.argv[2]) else: print USAGE ninix-aya-4.3.9/lib/ninix/dll/niseshiori.py000066400000000000000000001261151172114553600206330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # niseshiori.py - a "偽栞" compatible Shiori module for ninix # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import random import time import sys import logging import os import re REVISION = '$Revision: 1.28 $' ## FIXME # tree node types ADD_EXPR = 1 MUL_EXPR = 2 UNARY_EXPR = 3 PRIMARY_EXPR = 4 def list_dict(top_dir): buf = [] try: filelist = os.listdir(top_dir) except OSError: filelist = [] re_dict_filename = re.compile(r'^ai.*\.(dtx|txt)$') for filename in filelist: if re_dict_filename.match(filename): buf.append(os.path.join(top_dir, filename)) return buf class NiseShiori(object): def __init__(self): self.dict = {} self.type_chains = {} self.word_chains = {} self.keywords = {} self.responses = {} self.greetings = {} self.events = {} self.resources = {} self.variables = {} self.dbpath = None self.expr_parser = ExprParser() self.username = '' self.ai_talk_interval = 180 self.ai_talk_count = 0 self.surf0 = 0 self.surf1 = 10 self.event = None self.reference = None self.jump_entry = None self.motion_area = None self.motion_count = 0 self.mikire = 0 self.kasanari = 0 self.otherghost = [] self.to = '' self.sender = '' def load(self, top_dir): # read dictionaries dictlist = list_dict(top_dir) if not dictlist: return 1 for path in dictlist: self.read_dict(path) # read variables self.dbpath = os.path.join(top_dir, 'niseshiori.db') self.load_database(self.dbpath) return 0 def load_database(self, path): try: with open(path) as f: dic = {} for line in f: if line.startswith('# ns_st: '): try: self.ai_talk_interval = int(line[9:]) except ValueError: pass continue elif line.startswith('# ns_tn: '): self.username = unicode(line[9:].strip(), 'utf-8', 'ignore') continue try: name, value = [s.strip() for s in line.split('=')] except ValueError: logging.error( 'niseshiori.py: malformed database (ignored)') return dic[name] = unicode(value, 'utf-8', 'ignore') except IOError: return self.variables = dic def save_database(self): if self.dbpath is None: return try: with open(self.dbpath, 'w') as f: f.write('# ns_st: {0:d}\n'.format(self.ai_talk_interval)) f.write('# ns_tn: {0}\n'.format( self.username.encode('utf-8', 'ignore'))) print 'NS:', self.variables.keys() for name, value in self.variables.items(): if not name.startswith('_'): f.write('{0}={1}\n'.format( name, unicode(value).encode('utf-8'))) except IOError: logging.error('niseshiori.py: cannot write database (ignored)') return def finalize(self): pass re_type = re.compile(r'\\(m[szlchtep?]|[dk])') re_user = re.compile(r'\\u[a-z]') re_category = re.compile(r'\\(m[szlchtep]|[dk])?\[([^\]]+)\]') def read_dict(self, path): # read dict file and decrypt if necessary with open(path) as f: basename, ext = os.path.splitext(path) ext = ext.lower() if ext == '.dtx': buf = self.decrypt(f.read()) else: buf = f.readlines() # omit empty lines and comments and merge continuous lines definitions = [] decode = lambda line: unicode(line, 'Shift_JIS', 'replace') in_comment = 0 i, j = 0, len(buf) while i < j: line = buf[i].strip() i += 1 if not line: continue elif i == 1 and line.startswith('#Charset:'): charset = line[9:].strip() if charset in ['UTF-8', 'EUC-JP', 'EUC-KR']: # XXX decode = lambda line: unicode(line, charset) continue elif line.startswith('/*'): in_comment = 1 continue elif line.startswith('*/'): in_comment = 0 continue elif in_comment or line.startswith('#') or line.startswith('//'): continue lines = [line] while i < j and buf[i] and \ (buf[i].startswith(' ') or buf[i].startswith('\t')): lines.append(buf[i].strip()) i += 1 definitions.append(''.join(lines)) # parse each line for line in definitions: line = decode(line) # special case: words in a category match = self.re_category.match(line) if match: line = line[match.end():].strip() if not line or not line.startswith(','): self.syntax_error(path, line) continue words = [s.strip() for s in self.split(line) if s.strip()] cattype, catlist = match.groups() for cat in [s.strip() for s in self.split(catlist)]: if cattype is None: keylist = [(None, cat)] else: keylist = [(None, cat), (cattype, cat)] for key in keylist: value = self.dict.get(key, []) value.extend(words) self.dict[key] = value if cattype is not None: key = ''.join(('\\', cattype)) value = self.dict.get(key, []) value.extend(words) self.dict[key] = value continue # other cases try: command, argv = [s.strip() for s in self.split(line, 1)] except ValueError: self.syntax_error(path, line) continue if command == r'\ch': argv = [s.strip() for s in self.split(argv)] if len(argv) == 5: t1, w1, t2, w2, c = argv elif len(argv) == 4: t1, w1, t2, w2 = argv c = None elif len(argv) == 3: t1, w1, c = argv t2 = w2 = None else: self.syntax_error(path, line) continue if not self.re_type.match(t1) and not self.re_user.match(t1): self.syntax_error(path, line) continue if c is not None: ch_list = self.type_chains.get(t1, []) ch_list.append((c, w1)) self.type_chains[t1] = ch_list if t2 is None: continue if not self.re_type.match(t2) and not self.re_user.match(t2): self.syntax_error(path, line) continue if c is not None: ch_list = self.type_chains.get(t2, []) ch_list.append((c, w2)) self.type_chains[t2] = ch_list m1 = ''.join(('%', t1[1:])) m2 = ''.join(('%', t2[1:])) key = (m1, w1) dic = self.word_chains.get(key, {}) ch_list = dic.get(m2, []) if (c, w2) not in ch_list: ch_list.append((c, w2)) dic[m2] = ch_list self.word_chains[key] = dic key = (m2, w2) dic = self.word_chains.get(key, {}) ch_list = dic.get(m1, []) if (c, w1) not in ch_list: ch_list.append((c, w1)) dic[m1] = ch_list self.word_chains[key] = dic ch_list = self.dict.get(t1, []) if w1 not in ch_list: ch_list.append(w1) self.dict[t1] = ch_list ch_list = self.dict.get(t2, []) if w2 not in ch_list: ch_list.append(w2) self.dict[t2] = ch_list elif self.re_type.match(command) or self.re_user.match(command): words = [s.strip() for s in self.split(argv) if s.strip()] value = self.dict.get(command, []) value.extend(words) self.dict[command] = value elif command in [r'\dms', r'\e']: value = self.dict.get(command, []) value.append(argv) self.dict[command] = value elif command == r'\ft': argv = [s.strip() for s in self.split(argv, 2)] if len(argv) != 3: self.syntax_error(path, line) continue w, t, s = argv if not self.re_type.match(t): self.syntax_error(path, line) continue self.keywords[(w, t)] = s elif command == r'\re': argv = [s.strip() for s in self.split(argv, 1)] if len(argv) == 2: cond = self.parse_condition(argv[0]) re_list = self.responses.get(cond, []) re_list.append(argv[1]) self.responses[cond] = re_list elif command == r'\hl': argv = [s.strip() for s in self.split(argv, 1)] if len(argv) == 2: hl_list = self.greetings.get(argv[0], []) hl_list.append(argv[1]) self.greetings[argv[0]] = hl_list elif command == r'\ev': argv = [s.strip() for s in self.split(argv, 1)] if len(argv) == 2: cond = self.parse_condition(argv[0]) ev_list = self.events.get(cond, []) ev_list.append(argv[1]) self.events[cond] = ev_list elif command == r'\id': argv = [s.strip() for s in self.split(argv, 1)] if len(argv) == 2: if argv[0] in ['sakura.recommendsites', 'kero.recommendsites', 'sakura.portalsites']: id_list = self.resources.get(argv[0], '') if id_list: id_list = ''.join((id_list, '\2')) id_list = ''.join((id_list, argv[1].replace(' ', '\1'))) self.resources[argv[0]] = id_list else: self.resources[argv[0]] = argv[1] elif command == r'\tc': pass else: self.syntax_error(path, line) def split(self, line, maxcount=None): buf = [] count = 0 end = pos = 0 while maxcount is None or count < maxcount: pos = line.find(',', pos) if pos < 0: break elif pos > 0 and line[pos - 1] == '\\': pos += 1 else: buf.append(line[end:pos]) count += 1 end = pos = pos + 1 buf.append(line[end:]) return [s.replace('\\,', ',') for s in buf] def syntax_error(self, path, line): logging.debug( 'niseshiori.py: syntax error in {0}'.format( os.path.basename(path))) logging.debug(line) re_comp_op = re.compile('<[>=]?|>=?|=') COND_COMPARISON = 1 COND_STRING = 2 def parse_condition(self, condition): buf = [] for expr in [s.strip() for s in condition.split('&')]: match = self.re_comp_op.search(expr) if match: buf.append((self.COND_COMPARISON, ( expr[:match.start()].strip(), match.group(), expr[match.end():].strip()))) else: buf.append((self.COND_STRING, expr)) return tuple(buf) def decrypt(self, data): buf = [] a = 0x61 i = 0 j = len(data) line = [] while i < j: if data[i] == '@': i += 1 buf.append(''.join(line)) line = [] continue y = ord(data[i]) i += 1 x = ord(data[i]) i += 1 x -= a a += 9 y -= a a += 2 if a > 0xdd: a = 0x61 line.append(chr((x & 0x03) | ((y & 0x03) << 2) | \ ((y & 0x0c) << 2) | ((x & 0x0c) << 4))) return buf def getaistringrandom(self): result = self.get_event_response('OnNSRandomTalk') or self.get(r'\e') return result def get_event_response(self, event, ref0=None, ref1=None, ref2=None, ref3=None, ref4=None, ref5=None, ref6=None, ref7=None): ref = [ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7] if event in ['OnSecondChange', 'OnMinuteChange']: if ref[1]: self.mikire += 1 script = self.get_event_response('OnNSMikireHappen') if script is not None: return script elif self.mikire > 0: self.mikire = 0 return self.get_event_response('OnNSMikireSolve') if ref[2]: self.kasanari += 1 script = self.get_event_response('OnNSKasanariHappen') if script is not None: return script elif self.kasanari > 0: self.kasanari = 0 return self.get_event_response('OnNSKasanariHappen') if event == 'OnSecondChange' and self.ai_talk_interval > 0: self.ai_talk_count += 1 if self.ai_talk_count == self.ai_talk_interval: self.ai_talk_count = 0 if self.otherghost and \ random.randint(0, 10) == 0: target = [] for name, s0, s1 in self.otherghost: if name in self.greetings: target.append(name) if target: self.to = random.choice(target) if self.to: self.current_time = time.localtime(time.time()) s = random.choice(self.greetings[self.to]) if s: s = self.replace_meta(s) while 1: match = self.re_ns_tag.search(s) if not match: break value = self.eval_ns_tag(match.group()) s = ''.join((s[:match.start()], unicode(value), s[match.end():])) return s return self.getaistringrandom() return None elif event == 'OnMouseMove': if self.motion_area != (ref[3], ref[4]): self.motion_area = (ref[3], ref[4]) self.motion_count = 0 else: self.motion_count += 5 # sensitivity elif event == 'OnSurfaceChange': self.surf0 = ref[0] self.surf1 = ref[1] elif event == 'OnUserInput' and ref[0] == 'ninix.niseshiori.username': self.username = ref[1] self.save_database() return '\e' elif event == 'OnCommunicate': self.event = '' self.reference = (ref[1], ) if ref[0] == 'user': self.sender = 'User' else: self.sender = ref[0] candidate = [] for cond in self.responses: if self.eval_condition(cond): candidate.append(cond) script = None self.to = ref[0] if candidate: cond = random.choice(candidate) script = random.choice(self.responses[cond]) if not script and 'nohit' in self.responses: script = random.choice(self.responses['nohit']) if not script: self.to = '' else: script = self.replace_meta(script) while 1: match = self.re_ns_tag.search(script) if not match: break value = self.eval_ns_tag(match.group()) script = ''.join((script[:match.start()], unicode(value), script[match.end():])) self.sender = '' return script key = 'action' self.dict[key] = [] self.event = event self.reference = tuple(ref) self.current_time = time.localtime(time.time()) for condition, actions in self.events.items(): if self.eval_condition(condition): self.dict[key].extend(actions) script = self.get(key, default=None) if script is not None: self.ai_talk_count = 0 self.event = None self.reference = None if script is not None: script = script return script def otherghostname(self, ghost_list): self.otherghost = [] for ghost in ghost_list: name, s0, s1 = ghost.split(chr(1)) self.otherghost.append([unicode(name, 'utf-8', 'ignore'), s0, s1]) return '' def communicate_to(self): to = self.to self.to = '' return to def eval_condition(self, condition): for cond_type, expr in condition: if not self.eval_conditional_expression(cond_type, expr): return 0 return 1 def eval_conditional_expression(self, cond_type, expr): if cond_type == NiseShiori.COND_COMPARISON: value1 = self.expand_meta(expr[0]) value2 = expr[2] try: op1 = int(value1) op2 = int(value2) except ValueError: op1 = unicode(value1) op2 = unicode(value2) if expr[1] == '>=': return op1 >= op2 elif expr[1] == '<=': return op1 <= op2 elif expr[1] == '>': return op1 > op2 elif expr[1] == '<': return op1 < op2 elif expr[1] == '=': return op1 == op2 elif expr[1] == '<>': return op1 != op2 elif cond_type == NiseShiori.COND_STRING: if expr in self.event: return 1 for ref in self.reference: if ref is not None and expr in unicode(ref): return 1 return 0 else: return 0 re_ns_tag = re.compile(r'\\(ns_(st(\[[0-9]+\])?|cr|hl|rf\[[^\]]+\]|ce|tc\[[^\]]+\]|tn(\[[^\]]+\])?|jp\[[^\]]+\]|rt)|set\[[^\]]+\])') def get(self, key, default=''): self.current_time = time.localtime(time.time()) s = self.expand(key, '', default) if s: while 1: match = self.re_ns_tag.search(s) if not match: break value = self.eval_ns_tag(match.group()) s = ''.join((s[:match.start()], unicode(value), s[match.end():])) return s def eval_ns_tag(self, tag): value = '' if tag == r'\ns_cr': self.ai_talk_count = 0 elif tag == r'\ns_ce': self.to = '' elif tag == r'\ns_hl': if self.otherghost: self.to = random.choice(self.otherghost)[0] elif tag.startswith(r'\ns_st[') and tag.endswith(']'): try: num = int(tag[7:-1]) except ValueError: pass else: if num == 0: self.ai_talk_interval = 0 elif num == 1: self.ai_talk_interval = 420 elif num == 2: self.ai_talk_interval = 180 elif num == 3: self.ai_talk_interval = 60 else: self.ai_talk_interval = min(max(num, 4), 999) self.save_database() self.ai_talk_count = 0 elif tag.startswith(r'\ns_jp[') and tag.endswith(']'): name = tag[7:-1] self.jump_entry = name value = self.get_event_response('OnNSJumpEntry') or '' self.jump_entry = None elif tag.startswith(r'\set[') and tag.endswith(']'): statement = tag[5:-1] if '=' not in statement: logging.debug('niseshiori.py: syntax error') logging.debug(tag) else: name, expr = [x.strip() for x in statement.split('=', 1)] value = self.eval_expression(expr) if value is not None: self.variables[name] = value if not name.startswith('_'): self.save_database() logging.debug('set {0} = "{1}"'.format( name, value.encode('utf-8', 'ignore'))) else: logging.debug('niseshiori.py: syntax error') logging.debug(tag) elif tag == r'\ns_rt': value = r'\a' elif tag == r'\ns_tn': value = r'\![open,inputbox,ninix.niseshiori.username,-1]' elif tag.startswith(r'\ns_tn[') and tag.endswith(']'): self.username = tag[7:-1] self.save_database() return value re_meta = re.compile(r'%((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(self, s): pos = 0 buf = [] variables = {} variable_chains = [] while 1: match = self.re_meta.search(s, pos) if not match: buf.append(s[pos:]) break buf.append(s[pos:match.start()]) meta = match.group() if match.group(4) is not None: # %ms, %dms, %ua, etc. if meta not in variables: chained_meta = ''.join(('%', match.group(4))) for chains in variable_chains: if chained_meta in chains: candidates_A, candidates_B = chains[chained_meta] if candidates_A: word = random.choice(candidates_A) candidates_A.remove(word) else: word = random.choice(candidates_B) candidates_B.remove(word) if not candidates_A and not candidates_B: del chains[chained_meta] logging.debug('chained: {0} => {1}'.format( meta, word.encode('utf-8', 'ignore'))) break else: if match.group(4) == 'm?': word = self.expand( ''.join(('\\', random.choice(['ms', 'mz', 'ml', 'mc', 'mh', 'mt', 'me', 'mp']))), s) else: word = self.expand( ''.join(('\\', match.group(4))), s) chains = self.find_chains((chained_meta, word), s) prefix = 'chain:' for k, (candidates_A, candidates_B) in chains.items(): for w in candidates_A: logging.debug('{0} {1}, {2}'.format( prefix, k.encode('utf-8', 'ignore'), w.encode('utf-8', 'ignore'))) prefix = ' ' for w in candidates_B: logging.debug('{0} {1}, {2}'.format( prefix, k.encode('utf-8', 'ignore'), w.encode('utf-8', 'ignore'))) prefix = ' ' variables[meta] = word variable_chains.append(chains) buf.append(variables[meta]) else: buf.append(unicode(self.expand_meta(meta))) pos = match.end() t = ''.join(buf) return t def expand(self, key, context, default=''): choices = [] for keyword, word in self.type_chains.get(key, []): if keyword in context: logging.debug('chain keyword: {0}'.format( keyword.encode('utf-8', 'ignore'))) choices.append(word) if not choices: match = self.re_category.match(key) if match: key = match.groups() choices = self.dict.get(key) if not choices: if isinstance(key, tuple): key = u'({0}, {1})'.format(*key) logging.debug('{0} not found'.format( key.encode('utf-8', 'ignore'))) return default s = random.choice(choices) t = self.replace_meta(s) if isinstance(key, tuple): key = u'\\{0}[{1}]'.format(key[0] or '', key[1]) logging.debug( ''.join((key.encode('utf-8', 'ignore'), '=>', s.encode('utf-8', 'ignore')))) logging.debug( ''.join((' ' * len(key), '=>', t.encode('utf-8', 'ignore')))) return t def find_chains(self, key, context): chains = {} dic = self.word_chains.get(key, {}) for chained_meta in dic: candidates_A = [] candidates_B = [] for keyword, chained_word in dic[chained_meta]: if keyword and keyword in context: candidates_A.append(chained_word) else: candidates_B.append(chained_word) chains[chained_meta] = (candidates_A, candidates_B) return chains WEEKDAY_NAMES = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] def expand_meta(self, name): if name in ['%selfname', '%selfname2', '%keroname', '%friendname']: result = name elif name == '%sakuraname': result = '%selfname' elif name == '%username': result = self.username or '%username' elif name.startswith('%get[') and name.endswith(']'): value = self.variables.get(name[5:-1], '?') try: result = int(value) except ValueError: result = value elif name.startswith('%rand[') and name.endswith(']'): n = int(name[6:-1]) result = random.randint(1, n) elif name.startswith('%rand') and len(name) == 6: n = int(name[5]) result = random.randint(10**(n - 1), 10**n - 1) elif name.startswith('%ref') and len(name) == 5 and \ name[4] in '01234567': if self.reference is None: result = '' else: n = int(name[4]) result = '' if self.reference[n] is None else self.reference[n] elif name == '%jpentry': result = '' if self.jump_entry is None else self.jump_entry elif name == '%year': result = str(self.current_time[0]) elif name == '%month': result = str(self.current_time[1]) elif name == '%day': result = str(self.current_time[2]) elif name == '%hour': result = str(self.current_time[3]) elif name == '%minute': result = str(self.current_time[4]) elif name == '%second': result = str(self.current_time[5]) elif name == '%week': result = self.WEEKDAY_NAMES[self.current_time[6]] elif name == '%ns_st': result = self.ai_talk_interval elif name == '%surf0': result = str(self.surf0) elif name == '%surf1': result = str(self.surf1) elif name in ['%plathome', '%platform']: ## FIXME result = 'ninix' elif name == '%move': result = str(self.motion_count) elif name == '%mikire': result = str(self.mikire) elif name == '%kasanari': result = str(self.kasanari) elif name == '%ver': ## FIXME if REVISION[1:11] == 'Revision: ': result = u'偽栞 for ninix (rev.{0})'.format(REVISION[11:-2]) else: result = u'偽栞 for ninix' elif name == '%sender': result = self.sender if self.sender else '' elif name == '%ghost': if self.to: result = self.to elif self.otherghost: result = random.choice(self.otherghost)[0] else: result = '' else: result = ''.join(('\\', name)) return result def eval_expression(self, expr): tree = self.expr_parser.parse(expr) return None if tree is None else self.interp_expr(tree) def __interp_add_expr(self, tree): value = self.interp_expr(tree[0]) for i in range(1, len(tree), 2): operand = self.interp_expr(tree[i + 1]) try: if tree[i] == '+': value = int(value) + int(operand) elif tree[i] == '-': value = int(value) - int(operand) except ValueError: value = ''.join((value, tree[i], operand)) return value def __interp_mul_expr(self, tree): value = self.interp_expr(tree[0]) for i in range(1, len(tree), 2): operand = self.interp_expr(tree[i + 1]) try: if tree[i] == '*': value = int(value) * int(operand) elif tree[i] == '/': value = int(value) / int(operand) elif tree[i] == '\\': value = int(value) % int(operand) except (ValueError, ZeroDivisionError): value = ''.join((value, tree[i], operand)) return value def __interp_unary_expr(self, tree): operand = self.interp_expr(tree[1]) try: if tree[0] == '+': return int(operand) elif tree[0] == '-': return - int(operand) except ValueError: return ''.join((tree[0], operand)) def __interp_primary_expr(self, tree): if self.is_number(tree[0]): return int(tree[0]) elif tree[0].startswith('%'): return self.expand_meta(tree[0]) try: return self.variables[tree[0]] except KeyError: return '?' __expr = { ADD_EXPR: __interp_add_expr, MUL_EXPR: __interp_mul_expr, UNARY_EXPR: __interp_unary_expr, PRIMARY_EXPR: __interp_primary_expr, } def interp_expr(self, tree): key = tree[0] if key in self.__expr: return self.__expr[key](self, tree[1:]) else: raise RuntimeError, 'should not reach here' def is_number(self, s): return s and all([bool(c in '0123456789') for c in s]) class ExprError(ValueError): pass class ExprParser(object): def __init__(self): pass def show_progress(self, func, buf): if buf is None: logging.debug('{0}() -> syntax error'.format(func)) else: logging.debug('{0}() -> {1}'.format(func, buf)) re_token = re.compile(r'[()*/\+-]|\d+|\s+') def tokenize(self, data): buf = [] end = 0 while 1: match = NiseShiori.re_meta.match(data, end) if match: buf.append(match.group()) end = match.end() continue match = self.re_token.match(data, end) if match: if match.group().strip(): buf.append(match.group()) end = match.end() continue match = self.re_token.search(data, end) if match: buf.append(data[end:match.start()]) if match.group().strip(): buf.append(match.group()) end = match.end() else: if end < len(data): buf.append(data[end:]) break return buf def parse(self, data): self.tokens = self.tokenize(data) try: return self.get_expr() except ExprError: return None # syntax error # internal def done(self): return not self.tokens def pop(self): try: return self.tokens.pop(0) except IndexError: raise ExprError def look_ahead(self, index=0): try: return self.tokens[index] except IndexError: raise ExprError def match(self, s): if self.pop() != s: raise ExprError def get_expr(self): buf = self.get_add_expr() if not self.done(): raise ExprError self.show_progress('get_expr', buf) return buf def get_add_expr(self): buf = [ADD_EXPR] while 1: buf.append(self.get_mul_expr()) if self.done() or self.look_ahead() not in ['+', '-']: break buf.append(self.pop()) # operator if len(buf) == 2: buf = buf[1] self.show_progress('get_add_expr', buf) return buf def get_mul_expr(self): buf = [MUL_EXPR] while 1: buf.append(self.get_unary_expr()) if self.done() or self.look_ahead() not in ['*', '/', '\\']: break buf.append(self.pop()) # operator if len(buf) == 2: buf = buf[1] self.show_progress('get_mul_expr', buf) return buf def get_unary_expr(self): if self.look_ahead() in ['+', '-']: buf = [UNARY_EXPR, self.pop(), self.get_unary_expr()] else: buf = self.get_primary_expr() self.show_progress('get_unary_expr', buf) return buf def get_primary_expr(self): if self.look_ahead() == '(': self.match('(') buf = self.get_add_expr() self.match(')') else: buf = [PRIMARY_EXPR, self.pop()] self.show_progress('get_primary_expr', buf) return buf # <<< 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): def __init__(self, dll_name): NiseShiori.__init__(self) self.dll_name = dll_name def load(self, top_dir): NiseShiori.load(self, top_dir) return 1 def unload(self): NiseShiori.finalize(self) def find(self, top_dir, dll_name): result = 0 if list_dict(top_dir): result = 100 return result def show_description(self): 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-2012 by Shyouzou Sugitani\n' ' Copyright (C) 2003 by Shun-ichi TAHARA') def request(self, req_string): header = req_string.splitlines() req_header = {} line = header.pop(0) if line: line = line.strip() req_list = line.split() if len(req_list) >= 2: command = req_list[0].strip() protocol = req_list[1].strip() for line in header: line = unicode(line, 'utf-8', 'ignore').strip() ## FIXME if not line: continue if ':' not in line: continue key, value = [x.strip() for x in line.split(':', 1)] try: value = int(value) except: pass req_header[key] = value result = '' to = None if 'ID' in req_header: if req_header['ID'] == 'dms': result = self.get(r'\dms') elif req_header['ID'] == 'OnAITalk': result = self.getaistringrandom() elif req_header['ID'] in ['\\ms', '\\mz', '\\ml', '\\mc', '\\mh', '\\mt', '\\me', '\\mp']: result = self.get(req_header['ID']) elif req_header['ID'] == '\\m?': result = self.get(random.choice(['\\ms', '\\mz', '\\ml', '\\mc', '\\mh', '\\mt', '\\me', '\\mp'])) elif req_header['ID'] == 'otherghostname': otherghost = [] for n in range(128): if ''.join(('Reference', str(n))) in req_header: otherghost.append( req_header[''.join(('Reference', str(n)))]) result = self.otherghostname(otherghost) elif req_header['ID'] == 'OnTeach': if 'Reference0' in req_header: #self.teach(req_header['Reference0']) pass ## FIXME else: result = self.resources.get(req_header['ID']) if result is None: ref = [] for n in range(8): if ''.join(('Reference', str(n))) in req_header: ref.append( req_header[''.join(('Reference', str(n)))]) else: ref.append(None) ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7 = ref result = self.get_event_response( req_header['ID'], ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7) if result is None: result = '' to = self.communicate_to() result = 'SHIORI/3.0 200 OK\r\n' \ 'Sender: Niseshiori\r\n' \ 'Charset: UTF-8\r\n' \ 'Value: {0}\r\n'.format(result.encode('utf-8', 'ignore')) if to is not None: result = ''.join((result, 'Reference0: {0}\r\n'.format( to.encode('utf-8', 'ignore')))) result = ''.join((result, '\r\n')) return result def ns_open(shiori_dir): ns = NiseShiori() ns.load(shiori_dir) return ns def test(): if len(sys.argv) == 2: top_dir = sys.argv[1] else: top_dir = os.curdir if not list_dict(top_dir): print 'no dictionary' return logger = logging.getLogger() logger.setLevel(logging.DEBUG) # XXX ns = ns_open(top_dir) dump_dict(ns) while 1: print ns.getaistringrandom() try: raw_input() except: break def dump_dict(ns): print 'DICT' for k, v in ns.dict.items(): if isinstance(k, tuple): k = u'({0}, {1})'.format(*k) print k.encode('utf-8', 'ignore') for e in v: print '\t', e.encode('utf-8', 'ignore') print '*' * 50 print 'CHAINS' for t, chain_list in ns.type_chains.items(): print t.encode('utf-8', 'ignore') for c, w in chain_list: print (u'-> {0}, {1}'.format(c, w)).encode('utf-8', 'ignore') for key, dic in ns.word_chains.items(): print (u'({0}, {1})'.format(*key)).encode('utf-8', 'ignore') for t, chain_list in dic.items(): prefix = u'-> {0}'.format(t.encode('utf-8', 'ignore')) for c, w in chain_list: print prefix, (u'-> {0}, {1}'.format(c, w)).encode('utf-8', 'ignore') prefix = ''.join((' ', ' ' * len(t))) print '*' * 50 print 'KEYWORDS' for (t, w), s in ns.keywords.items(): print t.encode('utf-8', 'ignore'), w.encode('utf-8', 'ignore') print '->', s.encode('utf-8', 'ignore') print '*' * 50 print 'RESPONSES' for k, v in ns.responses.items(): print_condition(k) print '->', v print '*' * 50 print 'GREETINGS' for k, v in ns.greetings.items(): print k.encode('utf-8', 'ignore') print '->', v print '*' * 50 print 'EVENTS' for k, v in ns.events.items(): print_condition(k) for e in v: print '->', e.encode('utf-8', 'ignore') def print_condition(condition): prefix = 'Condition' for cond_type, expr in condition: if cond_type == NiseShiori.COND_COMPARISON: print prefix, (u"'{0}' '{1}' '{2}'".format(*expr)).encode('utf-8', 'ignore') elif cond_type == NiseShiori.COND_STRING: print prefix, "'{0}'".format(expr.encode('utf-8', 'ignore')) prefix = ' and' if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/dll/osuwari.py000066400000000000000000000161401172114553600201440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # osuwari.py - a Osuwari compatible Saori module for ninix # Copyright (C) 2006-2012 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. import glib import gtk from ninix.dll import SAORI import ninix.pix class Saori(SAORI): def __init__(self): SAORI.__init__(self) self.timeout_id = None self.settings = {} self.__sakura = None def need_ghost_backdoor(self, sakura): self.__sakura = sakura def check_import(self): return 1 if self.__sakura else 0 def setup(self): ## FIXME return 1 def execute(self, argument): if not argument: return self.RESPONSE[400] if argument[0] == 'START': if len(argument) < 7: return self.RESPONSE[400] try: assert argument[2] in ['ACTIVE', 'FIX'] or \ argument[2].stratswith('@') or \ argument[2].startswith('#') assert argument[3] in ['TL', 'TR', 'BL', 'BR'] self.settings['hwnd'] = argument[1] self.settings['target'] = argument[2] self.settings['position'] = argument[3] self.settings['offset_x'] = int(argument[4]) self.settings['offset_y'] = int(argument[5]) self.settings['timeout'] = int(argument[6]) self.settings['xmove'] = 0 self.settings['ymove'] = 0 self.settings['noclip'] = 0 if len(argument) > 7: if 'XMOVE' in argument[7]: self.settings['xmove'] = 1 if 'YMOVE' in argument[7]: self.settings['ymove'] = 1 if 'NOCLIP' in argument[7]: self.settings['noclip'] = 1 self.settings['except'] = ('DESKTOP', 'CENTER') if len(argument) > 8: #target, position = argument[8].split() # spec position, target = argument[8].split() # real world assert target in ['DESKTOP', 'WORKAREA'] assert position in ['TOP', 'LEFT', 'RIGHT', 'BOTTOM'] self.settings['except'] = (target, position) except: return self.RESPONSE[400] #self.timeout_id = glib.timeout_add(self.settings['timeout'], self.do_idle_tasks) self.timeout_id = glib.timeout_add(100, self.do_idle_tasks) return self.RESPONSE[204] elif argument[0] == 'STOP': if self.timeout_id is not None: glib.source_remove(self.timeout_id) self.timeout_id = None self.settings = {} return self.RESPONSE[204] else: return self.RESPONSE[400] def do_idle_tasks(self): if self.timeout_id is None: return False target = self.settings['target'] left, top, scrn_w, scrn_h = ninix.pix.get_workarea() target_flag = [False, False] if target == 'ACTIVE': active_window = self.get_active_window() if active_window: if self.__sakura.identify_window(active_window): target_flag[1] = True else: rect = active_window.get_frame_extents() target_x = rect.x target_y = rect.y target_w = rect.width target_h = rect.height target_flag[0] = True else: target_flag[1] = True elif target == 'FIX': ## FIXME target_x = left target_y = top target_w = scrn_w target_h = scrn_h target_flag[0] = True elif target.startswith('@'): ## FIXME #win = self.get_window_by_name(target[1:]) #if self.__sakura.identify_window(win): # return True #target_x, target_y = active_window.get_root_origin() #rect = active_window.get_frame_extents() #target_w = rect.width #target_h = rect.height pass elif target.startswith('#'): ## FIXME pass else: pass # should not reach here if not target_flag[0]: return target_flag[1] pos = self.settings['position'] scale = self.__sakura.get_surface_scale() offset_x = int(self.settings['offset_x'] * scale / 100) offset_y = int(self.settings['offset_y'] * scale / 100) if self.settings['hwnd'].startswith('s'): try: side = int(self.settings['hwnd'][1:]) except: return False else: try: side = int(self.settings['hwnd']) except: return False w, h = self.__sakura.get_surface_size(side) if pos[0] == 'T': y = target_y + offset_y elif pos[0] == 'B': y = target_y + target_h + offset_y - h else: return False # should not reach here if pos[1] == 'L': x = target_x + offset_x elif pos[1] == 'R': x = target_x + target_w + offset_x - w else: return False # should not reach here if not self.settings['noclip']: if x < left or x > left+ scrn_w or \ y < top or y > top + scrn_h: if self.settings['except'][1] == 'BOTTOM': pass ## FIXME elif self.settings['except'][1] == 'TOP': pass ## FIXME elif self.settings['except'][1] == 'LEFT': pass ## FIXME elif self.settings['except'][1] == 'RIGTH': pass ## FIXME elif self.settings['except'][1] == 'CENTER': pass ## FIXME else: pass # should not reach here self.__sakura.set_surface_position(side, x, y) self.__sakura.raise_surface(side) return True def get_window_by_name(self, name): ## FIXME return None def get_active_window(self): scrn = gtk.gdk.screen_get_default() if not scrn.supports_net_wm_hint('_NET_ACTIVE_WINDOW') or \ not scrn.supports_net_wm_hint('_NET_WM_WINDOW_TYPE'): return None active_window =scrn.get_active_window() if active_window is None: return None window_type = active_window.property_get('_NET_WM_WINDOW_TYPE')[-1][0] if window_type == '_NET_WM_WINDOW_TYPE_DESKTOP': return None else: return active_window ninix-aya-4.3.9/lib/ninix/dll/saori_cpuid.py000066400000000000000000000045661172114553600207650ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # saori_cpuid.py - a saori_cpuid compatible Saori module for ninix # Copyright (C) 2003-2012 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. # import random import platform from ninix.dll import SAORI class Saori(SAORI): entry = {'cpu.num': ['1', '2', '3', '5', '7', '11', '13'], 'cpu.vender': ['Inte1', 'AM0', 'VlA'], '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'], } def execute(self, argument): if not argument: return self.RESPONSE[400] if len(argument) > 1 and argument[1] == 0: return self.RESPONSE[204] value = '' if argument[0] == 'platform': value = 'ninix-aya' elif argument[0] == 'os.name': value = platform.system() elif argument[0] == 'os.version': value = platform.release() elif argument[0] == 'os.build': value = platform.version() elif argument[0] in self.entry: ## FIXME value = random.choice(self.entry[argument[0]]) if value: return 'SAORI/1.0 200 OK\r\nResult: {0}\r\n\r\n'.format(value) else: return self.RESPONSE[204] ninix-aya-4.3.9/lib/ninix/dll/satori.py000066400000000000000000002707711172114553600177700ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # satori.py - a "里々" compatible Shiori module for ninix # Copyright (C) 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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 # - マルチキャラクタ import os import sys import logging import re import time import random from ninix.home import get_normalized_path 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 encrypt(s): buf = [] t = len(s) p = (t + 1) / 2 for n in range(p): buf.append(s[n]) if len(s[p:]) > n: buf.append(s[-n - 1]) return buf def decrypt(s): buf = [] t = len(s) for n in range(0, t, 2): buf.append(s[n]) if t % 2 == 0: p = 1 else: p = 2 for n in range(p, t, 2): buf.append(s[-n]) return buf def list_dict(top_dir): buf = [] try: dir_list = os.listdir(top_dir) except OSError: dir_list = [] for filename in dir_list: basename, ext = os.path.splitext(filename) ext = ext.lower() if filename.lower().startswith('dic') and \ ext in ['.txt', '.sat'] or \ filename.lower() in ['replace.txt', 'replace_after.txt', 'satori_conf.txt', 'satori_conf.sat']: # XXX buf.append(os.path.join(top_dir, filename)) return buf class Filter(object): def __init__(self, rules): self.rules = [] for pat, rep in rules: self.rules.append((pat, rep)) def apply(self, text): for pat, rep in self.rules: text = text.replace(pat, rep) return text ### PARSER ### def read_tab_file(path, encrypted=0): lineno = 0 buf = [] with open(path) as f: for line in f: lineno += 1 if line.endswith('\r\n'): line = line[:-2] elif line.endswith('\r') or line.endswith('\n'): line = line[:-1] if encrypted: line = ''.join(decrypt(decrypt(line))) try: line = unicode(line, 'Shift_JIS') except UnicodeError as e: logging.debug('satori.py: {0} in {1} (line {2:d})'.format(e, path, lineno)) continue try: old, new = line.split('\t') except ValueError: logging.debug('satori.py: invalid line in {0} (line {1:d})'.format(path, lineno)) continue buf.append((old, new)) return buf class Parser(object): def __init__(self): self.talk = {} self.word = {} self.variable = [] self.parenthesis = 0 self.replace_filter = Filter([]) self.anchor_list = [] self.anchor_filter = Filter(self.anchor_list) self.is_anchor = 0 self.saori = [] self.separator = ['\1', ',', u',', u'、', u'、'] self.count = {'Talk': 0, 'NoNameTalk': 0, 'EventTalk': 0, 'OtherTalk': 0, 'Words': 0, 'Word': 0, 'Variable': 0, 'Anchor': 0, 'Parenthesis': 0, 'Parentheres': 0, ## XXX 'Line': 0,} def set_saori(self, saori_list): self.saori = saori_list def get_count(self, name): if name in self.count: return self.count[name] else: return 0 def load_replace_file(self, path): self.replace_filter = Filter(read_tab_file(path)) def get_dict(self): return self.talk, self.word def read(self, path): basename, ext = os.path.splitext(path) ext = ext.lower() if ext == '.sat': encrypted = 1 else: encrypted = 0 filename = os.path.basename(path) if filename.lower().startswith('dicanchor'): # XXX self.is_anchor = 1 else: self.is_anchor = 0 with open(path) as f: self.read_file(f, path, encrypted) if self.is_anchor: self.anchor_filter = Filter(self.anchor_list) def read_file(self, f, path=None, encrypted=0): lineno = 0 linelist = None line_buffer = None # XXX phi_escape = {} # key = lineno: [position] for line in f: if line_buffer is None: lineno += 1 phi_escape[lineno] = [] if line.endswith('\r\n'): line = line[:-2] elif line.endswith('\r') or line.endswith('\n'): line = line[:-1] if encrypted: line = ''.join(decrypt(decrypt(line))) try: line = unicode(line, 'Shift_JIS') except UnicodeError as e: if path is None: logging.debug('satori.py: {0} (line {1:d})'.format(e, lineno)) else: logging.debug('satori.py: {0} in {1} (line {2:d})'.format(e, path, lineno)) continue if line_buffer is not None: line = ''.join((line_buffer, line)) line_buffer = None pos = 0 while line.count(u'φ', pos) > 0: pos = line.find(u'φ', pos) if pos == len(line) - 1: line_buffer = line[:-1] break else: phi_escape[lineno].append(pos) ## FIXME line = ''.join((line[:pos], line[pos + 1:])) if line_buffer is not None: continue pos = 0 while line.count(u'#', pos) > 0: pos = line.find(u'#', pos) if pos not in phi_escape[lineno]: ## FIXME line = line[:pos] break if not line: continue if line.startswith(u'*') and 0 not in phi_escape[lineno]: if linelist: parser(linelist, phi_escape) parser = self.parse_talk linelist = [lineno, line] elif line.startswith(u'@') and 0 not in phi_escape[lineno]: if linelist: parser(linelist, phi_escape) parser = self.parse_word_group linelist = [lineno, line] elif linelist: # apply replace.txt line = self.replace_filter.apply(line) ## FIXME: phi_escape linelist.append(line) # for no in phi_escape: # if phi_escape[no]: # print 'PHI:', no, phi_escape[no] if linelist: parser(linelist, phi_escape) self.count['Line'] = self.count['Line'] + lineno talk = 0 eventtalk = 0 for key, value in self.talk.items(): number = len(value) talk += number if key.startswith('On'): eventtalk += number self.count['Talk'] = talk self.count['EventTalk'] = eventtalk if '' in self.talk: self.count['NoNameTalk'] = len(self.talk['']) self.count['OtherTalk'] = self.count['Talk'] \ - self.count['NoNameTalk'] \ - self.count['EventTalk'] self.count['Words'] = len(self.word) word = 0 for value in self.word.values(): word += len(value) self.count['Word'] = word self.count['Anchor'] = len(self.anchor_list) self.count['Variable'] = len(self.variable) self.count['Parenthesis'] = self.parenthesis self.count['Parentheres'] = self.parenthesis def parse_talk(self, linelist, phi_escape): lineno = linelist[0] buf = [] line = linelist[1] assert line.startswith(u'*') name = line[1:] while len(linelist) > 3 and not linelist[-1]: del linelist[-1] prev = '' num_open = 0 num_close = 0 for n in range(2, len(linelist)): line = linelist[n] num_open += line.count(u'(') ### FIXME: φ num_close += line.count(u')') ### FIXME: φ if num_open > 0 and num_open != num_close: if n == len(linelist) - 1: logging.debug( 'satori.py: syntax error (unbalanced parens)') else: prev = ''.join((prev, linelist[n])) continue else: num_open = 0 num_close = 0 line = ''.join((prev, linelist[n])) prev = '' current_lineno = lineno + n - 2 if line and line[0] == u'$' and 0 not in phi_escape[current_lineno]: node = self.parse_assignment(line) if node is not None: buf.append(node) elif line and line[0] == u'>' and 0 not in phi_escape[current_lineno]: node = self.parse_jump(line) if node is not None: buf.append(node) elif line and line[0] == u'≫' and 0 not in phi_escape[current_lineno]: node = self.parse_search(line) if node is not None: buf.append(node) elif line and line[0] == u'_' and 0 not in phi_escape[current_lineno]: node = self.parse_choice(line) if node is not None: buf.append(node) else: nodelist = self.parse_talk_word(line) if nodelist is not None: buf.extend(nodelist) if buf: try: talk_list = self.talk[name] except KeyError: talk_list = self.talk[name] = [] talk_list.append(buf) if self.is_anchor: self.anchor_list.append( [name, u'\\_a[{0}]{0}\\_a'.format(name)]) def parse_word_group(self, linelist, phi_escape): lineno = linelist[0] buf = [] line = linelist[1] assert line.startswith(u'@') name = line[1:] prev = '' num_open = 0 num_close = 0 for n in range(2, len(linelist)): line = linelist[n] num_open += line.count(u'(') ### FIXME: φ num_close += line.count(u')') ### FIXME: φ if num_open > 0 and num_open != num_close: if n == len(linelist) - 1: logging.debug( 'satori.py: syntax error (unbalanced parens)') else: prev = ''.join((prev, linelist[n])) continue else: num_open = 0 num_close = 0 line = ''.join((prev, linelist[n])) prev = '' if not line: continue word = self.parse_word(line) if word: buf.append(word) if buf: try: word_list = self.word[name] except KeyError: word_list = self.word[name] = [] word_list.extend(buf) def parse_assignment(self, line): assert line[0] == u'$' for n in range(1, len(line)): if line[n] in ['\t', ' ', u' ', u'=']: ### FIXME: φ break else: logging.debug('satori.py: syntax error (expected a tab or equal)') return None name_str = ''.join(line[1:n]) # XXX name = self.parse_word(line[1:n]) if line[n] == u'=': ### FIXME: φ n += 1 value = self.parse_expression(line[n:]) else: sep = line[n] while n < len(line) and line[n] == sep: n += 1 value = self.parse_word(line[n:]) if name_str == u'引数区切り削除': sep = ''.join(line[n:]) # XXX if sep in self.separator: self.separator.remove(sep) return None elif name_str == u'引数区切り追加': sep = ''.join(line[n:]) # XXX if sep not in self.separator: self.separator.append(sep) return None if name not in self.variable: self.variable.append(name) return [NODE_ASSIGNMENT, name, value] def parse_jump(self, line): assert line[0] == u'>' for n in range(1, len(line)): if line[n] == '\t': ### FIXME: φ break else: n = len(line) target = self.parse_word(line[1:n]) while n < len(line) and line[n] == '\t': ### FIXME: φ n += 1 if n < len(line): condition = self.parse_expression(line[n:]) else: condition = None return [NODE_JUMP, target, condition] def parse_search(self, line): return [NODE_SEARCH] def parse_choice(self, line): assert line[0] == u'_' for n in range(1, len(line)): if line[n] == '\t': ### FIXME: φ break else: n = len(line) label = self.parse_word(line[1:n]) while n < len(line) and line[n] == '\t': ### FIXME: φ n += 1 if n < len(line): id_ = self.parse_word(line[n:]) else: id_ = None return [NODE_CHOICE, label, id_] def parse_talk_word(self, line): buf = self.parse_word(line) buf.append([NODE_TEXT, [r'\n']]) return buf def parse_word(self, line): buffer_ = [] text = [] while line: if line[0] == u':': ### FIXME: φ if text: buffer_.append([NODE_TEXT, text]) text = [] buffer_.append([NODE_SIDE, [line[0]]]) line = line[1:] elif line[0] == u'(': ### FIXME: φ self.parenthesis += 1 if text: buffer_.append([NODE_TEXT, text]) text = [] line = self.parse_parenthesis(line, buffer_) else: text.append(line[0]) line = line[1:] if text: buffer_.append([NODE_TEXT, text]) return buffer_ def find_close(self, text, position): nest = 0 current = position while text[current:].count(u')') > 0: pos_new = text.index(u')', current) if pos_new == 0: break nest = text[position:pos_new].count(u'(') - text[position:pos_new].count(u')') if nest > 0: current = pos_new + 1 else: current = pos_new break return current def split_(self, text, sep): buf = [] pos_end = -1 while 1: position = text.find(u'(', pos_end + 1) if position == -1: ll = text[pos_end + 1:].split(sep) if len(buf) > 0: last = buf.pop(-1) buf.append(''.join((last, ll[0]))) buf.extend(ll[1:]) else: buf.extend(ll) break else: ll = text[pos_end + 1:position].split(sep) pos_end = self.find_close(text, position + 1) last = ll.pop(-1) ll.append(''.join((last, text[position:pos_end + 1]))) if len(buf) > 0: last = buf.pop(-1) buf.append(''.join((last, ll[0]))) buf.extend(ll[1:]) else: buf.extend(ll) buf = [x.strip() for x in buf] return buf def parse_parenthesis(self, line, buf): text_ = [] assert line[0] == u'(' line = line[1:] ## FIXME depth = 1 count = 1 while line: if line[0] == u')': ### FIXME: φ depth -= 1 if len(text_) != count: # () text_.append('') if depth == 0: line = line[1:] break text_.append(line[0]) line = line[1:] elif line[0] == u'(': ### FIXME: φ depth += 1 count += 1 text_.append(line[0]) line = line[1:] else: text_.append(line[0]) line = line[1:] if text_: sep = None pos = len(text_) for c in self.separator: ### FIXME: φ if text_.count(c) == 0: continue if text_.index(c) < pos: pos = text_.index(c) sep = c if sep is not None: list_ = self.split_(''.join(text_), sep) ## FIXME else: list_ = [''.join(text_)] if list_[0] in [u'単語の追加', 'sync', 'loop', 'call', 'set', 'remember', 'nop', u'合成単語群', 'when', 'times', 'while', 'for'] or list_[0].endswith(u'の数'): function = list_[0] args = [] if len(list_) > 1: for i in range(1, len(list_)): args.append(self.parse_expression(list_[i])) buf.append([NODE_CALL, function, args]) return line elif list_[0] in self.saori: function = list_[0] args = [] if len(list_) > 1: for i in range(1, len(list_)): # XXX: parse as text args.append(self.parse_word(list_[i])) buf.append([NODE_SAORI, function, args]) return line else: if len(list_) > 1: # XXX nodelist = [[NODE_TEXT, [u'(']]] nodelist.extend(self.parse_word(text_)) nodelist.append([NODE_TEXT, [u')']]) buf.append([NODE_REF, nodelist]) return line else: nodelist = [[NODE_TEXT, [u'(']]] nodelist.extend(self.parse_word(text_)) nodelist.append([NODE_TEXT, [u')']]) buf.append([NODE_REF, nodelist]) return line buf.append([NODE_TEXT, ['']]) # XXX return line def parse_expression(self, line): default = [[NODE_TEXT, line[:]]] try: line, buf = self.get_or_expr(line) except ValueError as e: return default if line: return default return buf def get_or_expr(self, line): line, and_expr = self.get_and_expr(line) buf = [NODE_OR_EXPR, and_expr] while line and line[0] in ['|', u'|']: ### FIXME: φ line = line[1:] if line and line[0] in ['|', u'|']: ### FIXME: φ line = line[1:] else: raise ValueError, 'broken OR operator' line, and_expr = self.get_and_expr(line) buf.append(and_expr) if len(buf) == 2: return line, buf[1] return line, [buf] def get_and_expr(self, line): line, comp_expr = self.get_comp_expr(line) buf = [NODE_AND_EXPR, comp_expr] while line and line[0] in ['&', u'&']: ### FIXME: φ line = line[1:] if line and line[0] in ['&', u'&']: ### FIXME: φ line = line[1:] else: raise ValueError, 'broken AND operator' line, comp_expr = self.get_comp_expr(line) buf.append(comp_expr) if len(buf) == 2: return line, buf[1] return line, [buf] def get_comp_expr(self, line): line, buf = self.get_add_expr(line) if line and line[0] in ['<', u'<']: ### FIXME: φ line = line[1:] op = '<' if line and line[0] in ['=', u'=']: ### FIXME: φ line = line[1:] op = '<=' line, add_expr = self.get_add_expr(line) return line, [[NODE_COMP_EXPR, buf, op, add_expr]] elif line and line[0] in ['>', u'>']: ### FIXME: φ line = line[1:] op = '>' if line and line[0] in ['=', u'=']: ### FIXME: φ line = line[1:] op = '>=' line, add_expr = self.get_add_expr(line) return line, [[NODE_COMP_EXPR, buf, op, add_expr]] elif line and line[0] in ['=', u'=']: ### FIXME: φ line = line[1:] if line and line[0] in ['=', u'=']: ### FIXME: φ line = line[1:] else: raise ValueError, 'broken EQUAL operator' line, add_expr = self.get_add_expr(line) return line, [[NODE_COMP_EXPR, buf, '==', add_expr]] elif line and line[0] in ['!', u'!']: ### FIXME: φ line = line[1:] if line and line[0] in ['=', u'=']: ### FIXME: φ line = line[1:] else: raise ValueError, 'broken NOT EQUAL operator' line, add_expr = self.get_add_expr(line) return line, [[NODE_COMP_EXPR, buf, '!=', add_expr]] return line, buf def get_add_expr(self, line): line, mul_expr = self.get_mul_expr(line) buf = [NODE_ADD_EXPR, mul_expr] while line and line[0] in ['+', u'+', '-', u'−']: ### FIXME: φ if line[0] in ['+', u'+']: buf.append('+') else: buf.append('-') line = line[1:] line, mul_expr = self.get_mul_expr(line) buf.append(mul_expr) if len(buf) == 2: return line, buf[1] return line, [buf] def get_mul_expr(self, line): line, pow_expr = self.get_pow_expr(line) buf = [NODE_MUL_EXPR, pow_expr] while line and \ line[0] in ['*', u'*', u'×', '/', u'/', u'÷', '%', u'%']: ### FIXME: φ if line[0] in ['*', u'*', u'×']: buf.append('*') elif line[0] in ['/', u'/', u'÷']: buf.append('/') else: buf.append('%') line = line[1:] line, pow_expr = self.get_pow_expr(line) buf.append(pow_expr) if len(buf) == 2: return line, buf[1] return line, [buf] def get_pow_expr(self, line): line, unary_expr = self.get_unary_expr(line) buf = [NODE_POW_EXPR, unary_expr] while line and line[0] in ['^', u'^']: ### FIXME: φ line = line[1:] line, unary_expr = self.get_unary_expr(line) buf.append(unary_expr) if len(buf) == 2: return line, buf[1] return line, [buf] def get_unary_expr(self, line): if line and line[0] in ['-', u'−']: ### FIXME: φ line = line[1:] line, unary_expr = self.get_unary_expr(line) return line, [[NODE_UNARY_EXPR, '-', unary_expr]] if line and line[0] in ['!', u'!']: ### FIXME: φ line = line[1:] line, unary_expr = self.get_unary_expr(line) return line, [[NODE_UNARY_EXPR, '!', unary_expr]] if line and line[0] == u'(': ### FIXME: φ line = line[1:] line, buf = self.get_or_expr(line) if line and line[0] == u')': ### FIXME: φ line = line[1:] else: raise ValueError, 'expected a close paren' return line, buf return self.get_factor(line) operators = [ '|', u'|', '&', u'&', '<', u'<', '>', u'>', '=', u'=', '!', u'!', '+', u'+', '-', u'−', '*', u'*', u'×', '/', u'/', u'÷', '%', u'%', '^', u'^', '(', ')'] def get_factor(self, line): buf = [] while line and line[0] not in self.operators: if line and line[0] == u'(': ### FIXME: φ line = self.parse_parenthesis(line, buf) continue text = [] while line and line[0] not in self.operators and line[0] != u'(': ### FIXME: φ text.append(line[0]) line = line[1:] if text: buf.append([NODE_TEXT, text]) if not buf: raise ValueError, 'expected a constant' return line, buf def print_nodelist(self, node_list, depth=0): for node in node_list: indent = ' ' * depth if node[0] == NODE_TEXT: print ''.join((indent, 'NODE_TEXT "{0}"'.format(''.join( [x.encode('utf-8') for x in node[1]])))) elif node[0] == NODE_REF: print ''.join((indent, 'NODE_REF')) self.print_nodelist(node[1], depth + 1) elif node[0] == NODE_CALL: print ''.join((indent, 'NODE_CALL')) for i in range(len(node[2])): self.print_nodelist(node[2][i], depth + 1) elif node[0] == NODE_SAORI: print ''.join((indent, 'NODE_SAORI')) for i in range(len(node[2])): self.print_nodelist(node[2][i], depth + 1) elif node[0] == NODE_SIDE: print ''.join((indent, 'NODE_SIDE')) elif node[0] == NODE_ASSIGNMENT: print ''.join((indent, 'NODE_ASSIGNMENT')) print ''.join((indent, 'variable')) self.print_nodelist(node[1], depth + 1) print ''.join((indent, 'value')) self.print_nodelist(node[2], depth + 1) elif node[0] == NODE_JUMP: print ''.join((indent, 'NODE_JUMP')) print ''.join((indent, 'name')) self.print_nodelist(node[1], depth + 1) if node[2] is not None: print ''.join((indent, 'condition')) self.print_nodelist(node[2], depth + 1) elif node[0] == NODE_SEARCH: print ''.join((indent, 'NODE_SEARCH')) elif node[0] == NODE_CHOICE: print ''.join((indent, 'NODE_CHOICE')) print ''.join((indent, 'label')) self.print_nodelist(node[1], depth + 1) if node[2] is not None: print ''.join((indent, 'id')) self.print_nodelist(node[2], depth + 1) elif node[0] == NODE_OR_EXPR: print ''.join((indent, 'NODE_OR_EXPR')) self.print_nodelist(node[1], depth + 1) for i in range(2, len(node)): print ''.join((indent, 'op ||')) self.print_nodelist(node[i], depth + 1) elif node[0] == NODE_AND_EXPR: print ''.join((indent, 'NODE_ADD_EXPR')) self.print_nodelist(node[1], depth + 1) for i in range(2, len(node)): print ''.join((indent, 'op &&')) self.print_nodelist(node[i], depth + 1) elif node[0] == NODE_COMP_EXPR: print ''.join((indent, 'NODE_COMP_EXPR')) self.print_nodelist(node[1], depth + 1) print ''.join((indent, 'op')), node[2] self.print_nodelist(node[3], depth + 1) elif node[0] == NODE_ADD_EXPR: print ''.join((indent, 'NODE_ADD_EXPR')) self.print_nodelist(node[1], depth + 1) for i in range(2, len(node), 2): print ''.join((indent, 'op')), node[i] self.print_nodelist(node[i + 1], depth + 1) elif node[0] == NODE_MUL_EXPR: print ''.join((indent, 'NODE_MUL_EXPR')) self.print_nodelist(node[1], depth + 1) for i in range(2, len(node), 2): print ''.join((indent, 'op')), node[i] self.print_nodelist(node[i + 1], depth + 1) elif node[0] == NODE_POW_EXPR: print ''.join((indent, 'NODE_POW_EXPR')) self.print_nodelist(node[1], depth + 1) for i in range(2, len(node)): print ''.join((indent, 'op ^')) self.print_nodelist(node[i], depth + 1) elif node[0] == NODE_UNARY_EXPR: print ''.join((indent, 'NODE_UNARY_EXPR')) print ''.join((indent, 'op')), node[1] self.print_nodelist(node[2], depth + 1) else: raise RuntimeError, 'should not reach here' # 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(object): DBNAME = 'satori_savedata.txt' EDBNAME = 'satori_savedata.sat' def __init__(self, satori_dir=os.curdir): self.satori_dir = satori_dir self.dbpath = os.path.join(satori_dir, self.DBNAME) self.saori_function = {} self.parser = Parser() self.reset() def reset(self): self.word = {} self.talk = {} self.variable = {} self.replace_filter = Filter([]) self.reset_surface = 1 self.mouse_move_count = {} self.mouse_wheel_count = {} self.touch_threshold = 60 self.touch_timeout = 2 self.current_surface = [0, 10] self.default_surface = [0, 10] self.add_to_surface = [0, 0] self.newline = r'\n[half]' self.newline_script = '' self.save_interval = 0 self.save_timer = 0 self.url_list = {} self.boot_script = None self.script_history = [None] * 64 self.wait_percent = 100 self.random_talk = -1 self.reserved_talk = {} self.silent_time = 0 self.choice_id = None self.choice_label = None self.choice_number = None self.timer = {} self.time_start = None self.runtime = 0 # accumulative self.runcount = 1 # accumulative self.folder_change = 0 self.saori_value = {} def load(self): buf = [] for path in list_dict(self.satori_dir): filename = os.path.basename(path) if filename == 'replace.txt': self.parser.load_replace_file(path) elif filename == 'replace_after.txt': self.load_replace_file(path) elif filename in ['satori_conf.txt', 'satori_conf.sat']: self.load_config_file(path) else: buf.append(path) for path in buf: self.parser.read(path) self.talk, self.word = self.parser.get_dict() self.load_database() self.time_start = time.time() self.get_event_response('OnSatoriLoad') self.boot_script = self.get_event_response('OnSatoriBoot') def load_config_file(self, path): parser = Parser() parser.read(path) talk, word = parser.get_dict() for nodelist in talk.get(u'初期化', []): self.expand(nodelist) def load_replace_file(self, path): self.replace_filter = Filter(read_tab_file(path)) def load_database(self): if self.variable.get(u'セーブデータ暗号化') == u'有効': encrypted = 1 self.dbpath = os.path.join(self.satori_dir, self.EDBNAME) else: encrypted = 0 self.dbpath = os.path.join(self.satori_dir, self.DBNAME) try: database = read_tab_file(self.dbpath, encrypted) except IOError: database = [] for name, value in database: self.assign(name, value) def save_database(self): if self.variable.get(u'セーブデータ暗号化') == u'有効': encrypted = 1 self.dbpath = os.path.join(self.satori_dir, self.EDBNAME) else: encrypted = 0 self.dbpath = os.path.join(self.satori_dir, self.DBNAME) try: with open(self.dbpath, 'w') as f: for name, value in self.variable.items(): if name in [u'前回終了時サーフェス0', u'前回終了時サーフェス1', u'デフォルトサーフェス0', u'デフォルトサーフェス1']: continue line = '{0}\t{1}'.format(name.encode('Shift_JIS'), value.encode('Shift_JIS')) if encrypted: line = ''.join(encrypt(encrypt(line))) f.write(line) f.write('\r\n') for side in [0, 1]: name = u'デフォルトサーフェス{0:d}'.format(side) value = self.to_zenkaku('{0:d}'.format(self.default_surface[side])) line = '{0}\t{1}'.format(name.encode('Shift_JIS'), value.encode('Shift_JIS')) if encrypted: line = ''.join(encrypt(encrypt(line))) f.write(line) f.write('\r\n') for side in [0, 1]: name = u'前回終了時サーフェス{0:d}'.format(side) value = self.to_zenkaku('{0:d}'.format(self.current_surface[side])) line = '{0}\t{1}'.format(name.encode('Shift_JIS'), value.encode('Shift_JIS')) if encrypted: line = ''.join(encrypt(encrypt(line))) f.write(line) f.write('\r\n') name = u'起動回数' value = self.to_zenkaku('{0:d}'.format(self.runcount)) line = '{0}\t{1}'.format(name.encode('Shift_JIS'), value.encode('Shift_JIS')) if encrypted: line = ''.join(encrypt(encrypt(line))) f.write(line) f.write('\r\n') for name in self.timer: value = self.to_zenkaku(self.timer[name]) line = '{0}\t{1}'.format(name.encode('Shift_JIS'), value.encode('Shift_JIS')) if encrypted: line = ''.join(encrypt(encrypt(line))) f.write(line) f.write('\r\n') for name in self.reserved_talk: value = self.to_zenkaku(self.reserved_talk[name]) line = '{0}\t{1}'.format(''.join((u'次から', value, u'回目のトーク')).encode('Shift_JIS'), name.encode('Shift_JIS')) if encrypted: line = ''.join(encrypt(encrypt(line))) f.write(line) f.write('\r\n') except IOError: logging.debug('satori.py: cannot write {0}'.format(self.dbpath)) return def finalize(self): self.get_event_response('OnSatoriUnload') accumulative_runtime = self.runtime + self.get_runtime() self.assign(u'単純累計秒', self.to_zenkaku(accumulative_runtime)) self.save_database() # SHIORI/1.0 API def getaistringrandom(self): return self.get_script('') def getaistringfromtargetword(self, word): return '' def getdms(self): return '' def getword(self, word_type): return '' # SHIORI/2.2 API EVENT_MAP = { 'OnFirstBoot': u'初回', 'OnBoot': u'起動', 'OnClose': u'終了', 'OnGhostChanging': u'他のゴーストへ変更', 'OnGhostChanged': u'他のゴーストから変更', 'OnVanishSelecting': u'消滅指示', 'OnVanishCancel': u'消滅撤回', 'OnVanishSelected': u'消滅決定', 'OnVanishButtonHold': u'消滅中断', } def get_event_response(self, event, ref0=None, ref1=None, ref2=None, ref3=None, ref4=None, ref5=None, ref6=None, ref7=None): self.event = event self.reference = [ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7] tail = '\e' if event == 'OnUpdateReady': try: ref0 = str(int(ref0) + 1) self.reference[0] = ref0 except: pass elif event == 'OnMouseMove': key = (ref3, ref4) # side, part count, timestamp = self.mouse_move_count.get(key, (0, 0)) if int(time.time() - timestamp) > self.touch_timeout: count = 0 count += 1 if count >= self.touch_threshold: event = u'{0}{1}なでられ'.format(str(ref3), str(ref4)) count = 0 self.mouse_move_count[key] = (count, time.time()) elif event == 'OnMouseWheel': key = (ref3, ref4) # side, part count, timestamp = self.mouse_wheel_count.get(key, (0, 0)) if int(time.time() - timestamp) > 2: count = 0 count += 1 if count >= 2: event = u'{0}{1}ころころ'.format(str(ref3), str(ref4)) count = 0 self.mouse_wheel_count[key] = (count, time.time()) elif event == 'OnSecondChange': self.silent_time += 1 if self.save_interval > 0: self.save_timer -= 1 if self.save_timer <= 0: self.save_database() self.save_timer = self.save_interval if ref3 != '0': # cantalk # check random talk timer if self.random_talk == 0: self.reset_random_talk_interval() elif self.random_talk > 0: self.random_talk -= 1 if self.random_talk == 0: event = self.get_reserved_talk() if event: self.reference[0] = self.to_zenkaku(1) else: self.reference[0] = self.to_zenkaku(0) self.reference[1] = event script = self.get_script('OnTalk') if script: self.script_history.pop(0) self.script_history.append(script) return script self.reference[0] = ref0 self.reference[1] = ref1 # check user-defined timers for name in self.timer.keys(): count = self.timer[name] - 1 if count > 0: self.timer[name] = count elif ref3 != '0': # cantalk del self.timer[name] event = name[:-6] break elif event == 'OnSurfaceChange': self.current_surface[0] = ref0 self.current_surface[1] = ref1 elif event == 'OnChoiceSelect': self.choice_id = ref0 self.choice_label = ref1 self.choice_number = ref2 if 'OnChoiceSelect' not in self.talk: event = ref0 elif event == 'OnChoiceEnter': self.choice_id = ref1 self.choice_label = ref0 self.choice_number = ref2 elif event == 'OnAnchorSelect': if ref0 in self.talk: event = ref0 elif event in ['sakura.recommendsites', 'sakura.portalsites', 'kero.recommendsites']: return self.get_url_list(event) elif event == 'OnRecommandedSiteChoice': script = self.get_url_script(ref0, ref1) if script: self.script_history.pop(0) self.script_history.append(script) return script elif event in self.EVENT_MAP: if event in ['OnBoot', 'OnGhostChanged']: if self.boot_script: script = self.boot_script self.boot_script = None self.script_history.pop(0) self.script_history.append(script) return script if event in ['OnClose', 'OnGhostChanging']: if event == 'OnClose': tail = r'\-\e' script = self.get_script('OnSatoriClose', tail=tail) if script: self.script_history.pop(0) self.script_history.append(script) return script if event not in self.talk: event = self.EVENT_MAP[event] # print 'EVENT:', event script = self.get_script(event, tail=tail) if script: self.script_history.pop(0) self.script_history.append(script) return script # SHIORI/2.4 API def teach(self, word): name = self.variable.get(u'教わること') if name is not None: self.variable[name] = word script = self.get_script(''.join((name, u'を教えてもらった'))) self.script_history.pop(0) self.script_history.append(script) return script return None # SHIORI/2.5 API def getstring(self, name): word = self.word.get(name) if word is not None: return self.expand(random.choice(word)) return None # internal def get_reserved_talk(self): reserved = None for key in self.reserved_talk: self.reserved_talk[key] -= 1 if self.reserved_talk[key] <= 0: reserved = key if reserved is not None: del self.reserved_talk[reserved] else: reserved = '' return reserved re_reservation = re.compile(u'次から((0|1|2|3|4|5|6|7|8|9|[0-9])+)(〜((0|1|2|3|4|5|6|7|8|9|[0-9])+))?回目のトーク') def assign(self, name, value): if name.endswith(u'タイマ'): if name[:-3] in self.talk: self.add_timer(name, value) elif name == u'全タイマ解除': if value == u'実行': self.delete_all_timers() elif name == u'辞書リロード': if value == u'実行': self.reload() elif name == u'手動セーブ': if value == u'実行': self.save_database() elif self.re_reservation.match(name): if not value: return None match = self.re_reservation.match(name) number = self.to_integer(match.group(1)) if match.group(4) is not None: number = random.randint(number, self.to_integer(match.group(4))) while 1: for key in self.reserved_talk: if self.reserved_talk[key] == number: number += 1 break else: break self.reserved_talk[value] = number elif name == u'次のトーク': if not value: return None number = 1 while 1: for key in self.reserved_talk: if self.reserved_talk[key] == number: number += 1 break else: break self.reserved_talk[value] = number elif name == u'トーク予約のキャンセル': if value == u'*': self.reserved_talk = {} elif value in self.reserved_talk: del self.reserved_talk[value] elif name == u'起動回数': self.runcount = self.to_integer(value) + 1 elif not value: if name in self.variable: del self.variable[name] else: self.variable[name] = value if name in [u'喋り間隔', u'喋り間隔誤差']: self.reset_random_talk_interval() elif name == u'教わること': return r'\![open,teachbox]' elif name == u'会話時サーフェス戻し': if value == u'有効': self.reset_surface = 1 elif value == u'無効': self.reset_surface = 0 elif name == u'デフォルトサーフェス0': value = self.to_integer(value) if value is not None: self.default_surface[0] = value elif name == u'デフォルトサーフェス1': value = self.to_integer(value) if value is not None: self.default_surface[1] = value elif name == u'サーフェス加算値0': value = self.to_integer(value) if value is not None: self.default_surface[0] = value self.add_to_surface[0] = value elif name == u'サーフェス加算値1': value = self.to_integer(value) if value is not None: self.default_surface[1] = value self.add_to_surface[1] = value elif name == u'単純累計秒': self.runtime = self.to_integer(value) elif name == u'なでられ反応回数': self.touch_threshold = self.to_integer(value) elif name == u'なでられ持続秒数': self.touch_timeout = self.to_integer(value) elif name == u'スコープ切り換え時': self.newline = value elif name == u'さくらスクリプトによるスコープ切り換え時': self.newline_script = value elif name == u'自動挿入ウェイトの倍率': if value.endswith('%'): value = value[:-1] elif value.endswith(u'%'): value = value[:-1] value = self.to_integer(value) if value is not None and value >= 0 and value <= 1000: self.wait_percent = value elif name == u'自動セーブ間隔': self.save_interval = self.to_integer(value) self.save_timer = self.save_interval elif name == u'辞書フォルダ': self.folder_change = 1 return None def change_folder(self): value = self.variable.get(u'辞書フォルダ') dir_list = value.split(',') self.parser = Parser() self.parser.set_saori(self.saori_function.keys()) for path in list_dict(self.satori_dir): filename = os.path.basename(path) if filename == 'replace.txt': self.parser.load_replace_file(path) elif filename == 'replace_after.txt': self.load_replace_file(path) buf = [] for dir_ in dir_list: dir_ = get_normalized_path(dir_, encode=0) dict_dir = os.path.join(self.satori_dir, dir_) if not os.path.isdir(dict_dir): logging.debug('satori.py: cannot read {0}'.format(dict_dir)) continue for path in list_dict(dict_dir): filename = os.path.basename(path) if filename == 'replace.txt': ## XXX self.parser.load_replace_file(path) elif filename == 'replace_after.txt': ## XXX self.load_replace_file(path) elif filename in ['satori_conf.txt', 'satori_conf.sat']: pass else: buf.append(path) for path in buf: self.parser.read(path) self.talk, self.word = self.parser.get_dict() def reload(self): self.finalize() self.reset() self.load() def reset_random_talk_interval(self): interval = self.get_integer(u'喋り間隔', 'ignore') if interval is None or interval == 0: self.random_talk = -1 return rate = self.get_integer(u'喋り間隔誤差', 'ignore') if rate is None: rate = 0.1 else: rate = min(max(rate, 1), 100) / 100.0 diff = int(interval * rate) self.random_talk = random.randint(interval - diff, interval + diff) def add_timer(self, name, value): count = self.to_integer(value) if count is None or count == 0: if name in self.timer: del self.timer[name] else: self.timer[name] = count def delete_all_timers(self): self.timer = {} def get_script(self, name, head=r'\1', tail=r'\e'): if self.reset_surface: self.current_reset_surface = [1, 1] else: self.current_reset_surface = [0, 0] script = self.get(name, default=None) if script is not None and script and script != '\\n': ##logging.debug('make("{0}")'.format(script.encode('utf-8'))) return self.make(''.join((head, script, tail))) return None def get_url_list(self, name): self.url_list[name] = [] list_ = self.talk.get(name) if list_ is None: return None url_list = '' for i in range(len(list_)-1, -1, -1): nodelist = list_[i] title = '' j = 0 while j < len(nodelist): node = nodelist[j] j += 1 if node[0] == NODE_TEXT: if node[1] == ['\\n']: break else: title = ''.join((title, ''.join(node[1]))) else: pass if not title: continue if title == '-': if url_list: url_list = ''.join((url_list, chr(2))) url_list = ''.join((url_list, title, chr(1))) continue url = '' while j < len(nodelist): node = nodelist[j] j += 1 if node[0] == NODE_TEXT: if node[1] == ['\\n']: break else: url = ''.join((url, ''.join(node[1]))) else: pass if not url: continue bannar = '' while j < len(nodelist): node = nodelist[j] j += 1 if node[0] == NODE_TEXT: if node[1] == ['\\n']: break else: bannar = ''.join((bannar, ''.join(node[1]))) else: pass if nodelist[j:]: script = nodelist[j:] else: script = None self.url_list[name].append([title, url, bannar, script]) if url_list: url_list = ''.join((url_list, chr(2))) url_list = ''.join((url_list, title, chr(1), url, chr(1), bannar)) if not url_list: url_list = None return url_list def get_url_script(self, title, url): script = None if self.reset_surface: self.current_reset_surface = [1, 1] else: self.current_reset_surface = [0, 0] for key in self.url_list: for item in self.url_list[key]: if item[0] == title and item[1] == url: if item[3] is not None: script = self.expand(item[3]) if script is not None and script and script != '\\n': script = self.make(''.join((r'\1', script, r'\e'))) break return script redundant_tags = [ (re.compile(r'(\\[01hu])+(\\[01hu])'), lambda m: m.group(2)), (re.compile(r'(\\n)+(\\e|$)'), lambda m: m.group(2)), (re.compile(r'(\\e)+'), lambda m: m.group(1)), ] re_newline = re.compile(r'((\\n)*)(\\e)') re_0 = re.compile(r'\\[0h]') re_1 = re.compile(r'\\[1u]') re_wait_after = re.compile(r'、|。|,|.') re_wait_before = re.compile(r'\\[01hunce]') re_tag = re.compile(r'\\[ehunjcxtqzy*v0123456789fmia!&+---]|' r'\\[sb][0-9]?|\\w[0-9]|\\_[wqslvVbe+cumna]|' r'\\__[ct]|\\URL') def make(self, script): if script is None: return None # make anchor buf = [] i = 0 while 1: match = self.re_tag.match(script, i) if match: start = match.start() end = match.end() buf.append(self.parser.anchor_filter.apply(script[i:start])) buf.append(script[start:end]) i = end else: buf.append(self.parser.anchor_filter.apply(script[i:])) break script = ''.join(buf) # apply replace_after.txt script = self.replace_filter.apply(script) # remove redundant tags for pattern, replace in self.redundant_tags: script, count = pattern.subn(replace, script) # remove redundant newline tags match = self.re_newline.search(script) if match: tag = match.group(3) if tag == r'\e': script = ''.join((script[:match.start()], tag, script[match.end():])) else: raise RuntimeError, 'should not reach here' # insert newline i = 1 while 1: match = self.re_0.search(script, i) if match: end = match.end() match = self.re_0.search(script, end) if match: start = match.start() if start < len(self.newline) or \ script[start - len(self.newline):start] != self.newline: script = ur'{0}{1}{2}'.format( script[:match.end()], self.newline_script, script[match.end():]) else: break i = end else: break i = 1 while 1: match = self.re_1.search(script, i) if match: end = match.end() match = self.re_1.search(script, end) if match: start = match.start() if start < len(self.newline) or \ script[start - len(self.newline):start] != self.newline: script = ur'{0}{1}{2}'.format( script[:match.end()], self.newline_script, script[match.end():]) else: break i = end else: break # insert waits buf = [] n = 0 i, j = 0, len(script) while i < j: match = self.re_wait_after.match(script, i) if match: buf.append(match.group()) buf.extend(self.make_wait(n)) n = 0 i = match.end() continue match = self.re_wait_before.match(script, i) if match: buf.extend(self.make_wait(n)) buf.append(match.group()) n = 0 i = match.end() continue if script[i] == '[': pos = script.find(']', i) if pos > i: buf.append(script[i:pos + 1]) i = pos + 1 continue match = self.re_tag.match(script, i) if match: buf.append(script[i:match.end()]) i = match.end() else: buf.append(script[i]) n += 3 i += 1 return ''.join(buf) def make_wait(self, ms): buf = [] n = (ms + 25) * self.wait_percent / 100 / 50 while n > 0: buf.append(r'\w{0:d}'.format(min(n, 9))) n -= 9 return buf def get(self, name, default=''): result = self.talk.get(name) if result is None: return default return self.expand(random.choice(result)) def expand(self, nodelist, caller_history=None, side=1): if nodelist is None: return '' buf = [] history = [] talk = 0 newline = [None, None] for node in nodelist: if node[0] == NODE_REF: if caller_history is not None: value = self.get_reference(node[1], caller_history, side) else: value = self.get_reference(node[1], history, side) if value: talk = 1 buf.append(value) history.append(value) if caller_history is not None: caller_history.append(value) elif node[0] == NODE_CALL: function = node[1] args = node[2] value = self.call_function( function, args, caller_history if caller_history is not None else history, side) if value: talk = 1 buf.append(value) history.append(value) if caller_history is not None: caller_history.append(value) elif node[0] == NODE_SAORI: if self.variable.get(u'SAORI引数の計算') == u'無効': expand_only = 1 else: expand_only = 0 if caller_history is not None: value = self.call_saori( node[1], self.calc_args(node[2], caller_history, expand_only), caller_history, side) else: value = self.call_saori( node[1], self.calc_args(node[2], history, expand_only), history, side) if value: talk = 1 buf.append(value) history.append(value) if caller_history is not None: caller_history.append(value) elif node[0] == NODE_TEXT: buf.extend(node[1]) talk = 1 elif node[0] == NODE_SIDE: if talk: newline[side] = self.newline else: newline[side] = None talk = 0 if side == 0: side = 1 else: side = 0 buf.append(r'\{0:d}'.format(side)) if self.current_reset_surface[side]: buf.append(r'\s[{0:d}]'.format(self.default_surface[side])) self.current_reset_surface[side] = 0 if newline[side] is not None: buf.append(r'{0}'.format(newline[side])) elif node[0] == NODE_ASSIGNMENT: value = self.expand(node[2]) result = self.assign(self.expand(node[1]), value) if result is not None: buf.append(result) elif node[0] == NODE_JUMP: if node[2] is None or \ self.expand(node[2]) not in [u'0', '0']: target = self.expand(node[1]) if target == 'OnTalk': self.reference[1] = self.get_reserved_talk() if self.reference[1]: self.reference[0] = self.to_zenkaku(1) else: self.reference[0] = self.to_zenkaku(0) script = self.get(target, default=None) if script is not None and script and script != '\\n': buf.append(''.join((r'\1', script))) break elif node[0] == NODE_SEARCH: ## FIXME buf.append('') elif node[0] == NODE_CHOICE: label = self.expand(node[1]) if node[2] is None: id_ = label else: id_ = self.expand(node[2]) buf.append(ur'\q[{0},{1}]\n'.format(label, id_)) talk = 1 elif node[0] == NODE_OR_EXPR: for i in range(1, len(node)): if self.expand(node[i]) not in [u'0', '0']: buf.append(u'1') break else: buf.append(u'0') elif node[0] == NODE_AND_EXPR: for i in range(1, len(node)): if self.expand(node[i]) in [u'0', '0']: buf.append(u'0') break else: buf.append(u'1') elif node[0] == NODE_COMP_EXPR: operand1 = self.expand(node[1]) operand2 = self.expand(node[3]) n1 = self.to_integer(operand1) n2 = self.to_integer(operand2) if not (n1 is None or n2 is None): operand1 = n1 operand2 = n2 elif n1 is None and n2 is None and \ node[2] in ['<', '>', '<=', '>=']: operand1 = len(operand1) operand2 = len(operand2) if node[2] == '==': buf.append(self.to_zenkaku(int(operand1 == operand2))) elif node[2] == '!=': buf.append(self.to_zenkaku(int(operand1 != operand2))) elif node[2] == '<': buf.append(self.to_zenkaku(int(operand1 < operand2))) elif node[2] == '>': buf.append(self.to_zenkaku(int(operand1 > operand2))) elif node[2] == '<=': buf.append(self.to_zenkaku(int(operand1 <= operand2))) elif node[2] == '>=': buf.append(self.to_zenkaku(int(operand1 >= operand2))) else: raise RuntimeError, 'should not reach here' elif node[0] == NODE_ADD_EXPR: value_str = self.expand(node[1]) value = self.to_integer(value_str) for i in range(2, len(node), 2): operand_str = self.expand(node[i + 1]) operand = self.to_integer(operand_str) if node[i] == '-': if value is None or operand is None: value_str = value_str.replace(operand_str, '') value = None else: value -= operand value_str = self.to_zenkaku(value) continue if value is None: value = 0 if operand is None: operand = 0 if node[i] == '+': value += operand else: raise RuntimeError, 'should not reach here' if value is None: buf.append(value_str) else: buf.append(self.to_zenkaku(value)) elif node[0] == NODE_MUL_EXPR: value_str = self.expand(node[1]) value = self.to_integer(value_str) for i in range(2, len(node), 2): operand_str = self.expand(node[i + 1]) operand = self.to_integer(operand_str) if node[i] == '*': if value is None and operand is None: value_str = '' value = None elif value is None: value_str *= operand value = None elif operand is None: value_str *= operand_str value = None else: value *= operand continue if value is None: value = 0 if operand is None: operand = 0 if node[i] == '/': value /= operand elif node[i] == '%': value %= operand else: raise RuntimeError, 'should not reach here' if value is None: buf.append(value_str) else: buf.append(self.to_zenkaku(value)) elif node[0] == NODE_POW_EXPR: value = self.to_integer(self.expand(node[1])) if value is None: value = 0 for i in range(2, len(node)): operand = self.to_integer(self.expand(node[i])) if operand is None: operand = 0 value **= operand buf.append(self.to_zenkaku(value)) elif node[0] == NODE_UNARY_EXPR: value = self.expand(node[2]) if node[1] == '-': value = self.to_integer(value) if value is None: value = 0 value = -value elif node[1] == '!': value = int(value in [u'0', '0']) else: raise RuntimeError, 'should not reach here' buf.append(self.to_zenkaku(value)) else: raise RuntimeError, 'should not reach here' return ''.join(buf).strip() re_random = re.compile(u'乱数((−|+|[-+])?(0|1|2|3|4|5|6|7|8|9|[0-9])+)〜((−|+|[-+])?(0|1|2|3|4|5|6|7|8|9|[0-9])+)') re_is_empty = re.compile(u'(変数|文|単語群)「(.*)」の存在') re_n_reserved = re.compile(u'次から((0|1|2|3|4|5|6|7|8|9|[0-9])+)回目のトーク') re_is_reserved = re.compile(u'トーク「(.*)」の予約有無') def get_reference(self, nodelist, history, side): key = self.expand(nodelist[1:-1], history) if key and key[0] in [u'R', 'R']: n = self.to_integer(key[1:]) if n is not None and 0 <= n < len(self.reference): ## FIXME if self.reference[n] is None: return '' return unicode(self.reference[n]) elif key and key[0] in [u'S', 'S']: n = self.to_integer(key[1:]) if n is not None: if key in self.saori_value: if self.saori_value[key] is None: return '' return unicode(self.saori_value[key]) else: return '' elif key and key[0] in [u'H', 'H']: ##logging.debug(''.join(('["', '", "'.join(history), '"]'))) n = self.to_integer(key[1:]) if n is not None and 1 <= n < len(history) + 1: ## FIXME return history[n-1] n = self.to_integer(key) if n is not None: return r'\s[{0:d}]'.format(n + self.add_to_surface[side]) if key in self.word: return self.expand(random.choice(self.word[key]), history, side=side) elif key in self.talk: self.reference = [None] * 8 return self.expand(random.choice(self.talk[key]), side=1) elif key in self.variable: return self.variable[key] elif key in self.timer: return self.to_zenkaku(self.timer[key]) elif self.is_reserved(key): return self.get_reserved(key) elif self.re_random.match(key): match = self.re_random.match(key) i = self.to_integer(match.group(1)) j = self.to_integer(match.group(4)) if i < j: return self.to_zenkaku(random.randint(i, j)) else: return self.to_zenkaku(random.randint(j, i)) elif self.re_n_reserved.match(key): match = self.re_n_reserved.match(key) number = self.to_integer(match.group(1)) for key in self.reserved_talk: if self.reserved_talk[key] == number: return key else: return '' elif self.re_is_reserved.match(key): match = self.re_is_reserved.match(key) name = match.group(1) if name in self.reserved_talk: return self.to_zenkaku(1) else: return self.to_zenkaku(0) elif self.re_is_empty.match(key): match = self.re_is_empty.match(key) type_ = match.group(1) name = match.group(2) if type_ == u'変数': if name in self.variable: return self.to_zenkaku(1) else: return self.to_zenkaku(0) elif type_ == u'文': if name in self.talk: return self.to_zenkaku(1) else: return self.to_zenkaku(0) elif type_ == u'単語群': if name in self.word: return self.to_zenkaku(1) else: return self.to_zenkaku(0) return u'({0})'.format(key) def calc_args(self, args, history, expand_only=0): buf = [] for i in range(len(args)): value = self.expand(args[i], history) line = value ## FIXME if expand_only or not line: buf.append(value) elif line[0] in ['-', u'−', '+', u'+'] and len(line) == 1: # XXX buf.append(value) elif line[0] in self.NUMBER: try: ## FIXME line, expr = self.parser.get_add_expr(line) result = str(self.to_integer(self.expand(expr, history))) if result is None: buf.append(value) else: buf.append(result) except: buf.append(value) else: buf.append(value) return buf def call_function(self, name, args, history, side): if name == u'単語の追加': pass ## FIXME elif name == 'call': ref = self.expand(args[0], history) args = args[1:] for i in range(len(args)): name = ''.join((u'A', self.to_zenkaku(i))) self.variable[name] = self.expand(args[i], history) result = self.get_reference([[NODE_TEXT, u'('], [NODE_TEXT, ref], [NODE_TEXT, u')']], history, side) for i in range(len(args)): name = ''.join((u'A', self.to_zenkaku(i))) del self.variable[name] return result elif name == 'remember': number = self.to_integer(self.expand(args[0], history)) if number > 0 and number <= 64 and self.script_history[-number]: return self.script_history[-number] else: return '' elif name == 'loop': ref = self.expand(args[0], history) if len(args) < 2: return '' elif len(args) == 2: start = 1 end = self.to_integer(self.expand(args[1], history)) + 1 step = 1 elif len(args) == 3: start = self.to_integer(self.expand(args[1], history)) end = self.to_integer(self.expand(args[2], history)) + 1 step = 1 elif len(args) >= 4: start = self.to_integer(self.expand(args[1], history)) end = self.to_integer(self.expand(args[2], history)) step = self.to_integer(self.expand(args[3], history)) if step > 0: end = end + 1 elif step < 0: end = end - 1 else: return '' # infinite loop name = ''.join((ref, u'カウンタ')) buf = [] for i in range(start, end, step): self.variable[name] = self.to_zenkaku(i) buf.append(self.get_reference([[NODE_TEXT, u'('], [NODE_TEXT, ref], [NODE_TEXT, u')']], history, side)) return ''.join(buf) elif name == 'sync': ## FIXME pass elif name == 'set': if not args: name = '' else: name = self.expand(args[0], history) if len(args) < 2: value = '' else: value = self.expand(args[1], history) if name: if not value: if name in self.variable: del self.variable[name] else: self.variable[name] = value return '' elif name == 'nop': for i in range(len(args)): self.expand(args[i], history) pass elif name == u'合成単語群': ## FIXME: not tested words = [] for i in range(len(args)): ## FIXME name = self.expand(args[i], history) word = self.word.get(name) if word is not None: words.extend(word) if words: return self.expand(random.choice(words)) elif name.endswith(u'の数'): if name[0] in ['R', u'R']: return len(self.reference) elif name[0] in ['A', u'A']: # len(args) pass ## FIXME elif name[0] in ['S', u'S']: ##return len(self.saori_value) pass ## FIXME #elif name[0] in ['H', u'H']: # return len(history) #elif name[0] in ['C', u'C']: # return len(count) else: ## FIXME pass elif name == 'when': assert len(args) > 1 condition = self.expand(args[0], history) if condition in [u'0', '0']: if len(args) > 2: return self.expand(args[2], history) else: return '' else: return self.expand(args[1], history) elif name == 'times': ## FIXME print 'TIMES:', len(args), args pass elif name == 'while': ## FIXME print 'WHILE:', len(args), args pass elif name == 'for': ## FIXME print 'FOR:', len(args), args pass else: raise RuntimeError, 'should not reach here' return '' def call_saori(self, name, args, history, side): return '' def get_runtime(self): return int(time.time() - self.time_start) def get_integer(self, name, error='strict'): value = self.variable.get(name) if value is None: return None return self.to_integer(value, error) NUMBER = { u'0': '0', '0': '0', u'1': '1', '1': '1', u'2': '2', '2': '2', u'3': '3', '3': '3', u'4': '4', '4': '4', u'5': '5', '5': '5', u'6': '6', '6': '6', u'7': '7', '7': '7', u'8': '8', '8': '8', u'9': '9', '9': '9', u'+': '+', '+': '+', u'−': '-', '-': '-', } def to_integer(self, line, error='strict'): buf = [] for char in line: try: buf.append(self.NUMBER[char]) except KeyError: if char in [u'.', '.']: # XXX buf.append('.') elif error == 'strict': return None try: return int(''.join(buf)) except ValueError: if '.' in buf: # XXX return int(float(''.join(buf))) return None def is_number(self, line): return self.to_integer(line) is not None ZENKAKU = { '0': u'0', '1': u'1', '2': u'2', '3': u'3', '4': u'4', '5': u'5', '6': u'6', '7': u'7', '8': u'8', '9': u'9', '+': u'+', '-': u'−', } def to_zenkaku(self, s): buf = list(str(s)) for i in range(len(buf)): buf[i] = self.ZENKAKU.get(buf[i], buf[i]) return ''.join(buf) RESERVED = [ u'現在年', u'現在月', u'現在日', u'現在曜日', u'現在時', u'現在分', u'現在秒', u'起動時', u'起動分', u'起動秒', u'累計時', u'累計分', u'累計秒', u'OS起動時', u'OS起動分', u'OS起動秒', u'単純起動分', u'単純起動秒', u'単純累計分', u'単純累計秒', u'単純OS起動分', u'単純OS起動秒', u'最終トークからの経過秒', u'サーフェス0', u'サーフェス1', u'選択ID', u'選択ラベル', u'選択番号', u'予約トーク数', u'起動回数', '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(self, s): return s in self.RESERVED DAYOFWEEK = [u'月', u'火', u'水', u'木', u'金', u'土', u'日'] def get_reserved(self, s): now = time.localtime(time.time()) if s == u'現在年': return self.to_zenkaku(now[0]) elif s == u'現在月': return self.to_zenkaku(now[1]) elif s == u'現在日': return self.to_zenkaku(now[2]) elif s == u'現在時': return self.to_zenkaku(now[3]) elif s == u'現在分': return self.to_zenkaku(now[4]) elif s == u'現在秒': return self.to_zenkaku(now[5]) elif s == u'現在曜日': return self.DAYOFWEEK[now[6]] runtime = self.get_runtime() if s == u'起動時': return self.to_zenkaku(runtime / 3600) elif s == u'起動分': return self.to_zenkaku(runtime / 60 % 60) elif s == u'起動秒': return self.to_zenkaku(runtime % 60) elif s == u'単純起動分': return self.to_zenkaku(runtime / 60) elif s == u'単純起動秒': return self.to_zenkaku(runtime) accumulative_runtime = self.runtime + self.get_runtime() if s == u'累計時': return self.to_zenkaku(accumulative_runtime / 3600) elif s == u'累計分': return self.to_zenkaku(accumulative_runtime / 60 % 60) elif s == u'累計秒': return self.to_zenkaku(accumulative_runtime % 60) elif s == u'単純累計分': return self.to_zenkaku(accumulative_runtime / 60) elif s == u'単純累計秒': return self.to_zenkaku(accumulative_runtime) elif s == u'起動回数': return self.to_zenkaku(self.runcount) elif s == u'最終トークからの経過秒': return self.to_zenkaku(self.silent_time) elif s == u'サーフェス0': return str(self.current_surface[0]) elif s == u'サーフェス1': return str(self.current_surface[1]) elif s == u'選択ID': if self.choice_id is None: return '' else: return self.choice_id elif s == u'選択ラベル': if self.choice_label is None: return '' else: return self.choice_label elif s == u'選択番号': if self.choice_number is None: return '' else: return self.to_zenkaku(self.choice_number) elif s == u'予約トーク数': return self.to_zenkaku(len(self.reserved_talk)) elif s == 'Sender': return 'ninix' elif s == 'Event': return self.event elif s == 'Charset': return 'EUC-JP' elif s.startswith('Reference'): n = int(s[9:]) if self.reference[n] is None: return '' return unicode(self.reference[n]) elif s.startswith('count'): return self.to_zenkaku(self.parser.get_count(s[5:])) return unicode('?', 'utf-8') class Shiori(Satori): def __init__(self, dll_name): self.dll_name = dll_name self.saori = None self.saori_function = {} def use_saori(self, saori): self.saori = saori def load(self, satori_dir): Satori.__init__(self, satori_dir) self.saori_library = SatoriSaoriLibrary(self.saori, self) Satori.load(self) return 1 def load_config_file(self, path): parser = Parser() parser.read(path) talk, word = parser.get_dict() for nodelist in talk.get(u'初期化', []): self.expand(nodelist) self.saori_function = {} for nodelist in word.get('SAORI', []): if nodelist[0][0] == NODE_TEXT: list_ = ''.join(nodelist[0][1]).split(',') if len(list_) >= 2 and list_[0] and list_[1]: head, tail = os.path.split(list_[1]) saori_dir = os.path.join(self.satori_dir, head) result = self.saori_library.load(list_[1], saori_dir) if result: self.saori_function[list_[0]] = list_[1:] else: self.saori_function[list_[0]] = None ##logging.error('satori.py: cannot load {0}'.format(list_[1])) self.parser.set_saori(self.saori_function.keys()) def reload(self): self.finalize() self.reset() self.load(self.satori_dir) def unload(self): Satori.finalize(self) self.saori_library.unload() def find(self, top_dir, dll_name): result = 0 if list_dict(top_dir): result = 100 return result def show_description(self): 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-2012 by Shyouzou Sugitani\n' ' Copyright (C) 2003, 2004 by Shun-ichi TAHARA') def request(self, req_string): if self.folder_change: self.change_folder() self.folder_change = 0 header = unicode(req_string, 'EUC-JP').splitlines() req_header = {} line = header.pop(0) if line: line = line.strip() req_list = line.split() if len(req_list) >= 2: command = req_list[0].strip() protocol = req_list[1].strip() for line in header: line = line.strip() if not line: continue if ':' not in line: continue key, value = [x.strip() for x in line.split(':', 1)] try: value = int(value) except: value = unicode(value) req_header[key] = value result = '' to = None if 'ID' in req_header: if req_header['ID'] == 'dms': result = self.getdms() elif req_header['ID'] == 'OnAITalk': result = self.getaistringrandom() elif req_header['ID'] in ['\\ms', '\\mz', '\\ml', '\\mc', '\\mh', \ '\\mt', '\\me', '\\mp', '\\m?']: result = self.getword(req_header['ID']) elif req_header['ID'] == 'otherghostname': ## FIXME ##otherghost = [] ##for n in range(128): ## if ''.join(('Reference', str(n))) in req_header: ## otherghost.append(req_header[''.join(('Reference', ## str(n)]))) ##result = self.otherghostname(otherghost) pass elif req_header['ID'] == 'OnTeach': if 'Reference0' in req_header: self.teach(req_header['Reference0']) else: result = self.getstring(req_header['ID']) if result is None: ref = [] for n in range(8): if ''.join(('Reference', str(n))) in req_header: ref.append(req_header[ ''.join(('Reference', str(n)))]) else: ref.append(None) ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7 = ref result = self.get_event_response( req_header['ID'], ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7) if result is None: result = '' to = None ##self.communicate_to() ## FIXME if result != '': self.silent_time = 0 result = 'SHIORI/3.0 200 OK\r\n' \ 'Sender: Satori\r\n' \ 'Charset: EUC-JP\r\n' \ u'Value: {0}\r\n'.format(result) if to is not None: result = ''.join((result, u'Reference0: {0}\r\n'.format(to))) result = ''.join((result, '\r\n')) return result.encode('EUC-JP') ## FIXME def call_saori(self, name, args, history, side): if name not in self.saori_function or \ self.saori_function[name] is None: return '' 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' default_args = self.saori_function[name][1:] n = len(default_args) for i in range(len(default_args)): req = ''.join((req, u'Argument{0}: {1}\r\n'.format(i, default_args[i]))) for i in range(len(args)): argument = args[i] if argument: req = ''.join((req, u'Argument{0}: {1}\r\n'.format(i + n, argument))) req = ''.join((req, '\r\n')) response = self.saori_library.request( self.saori_function[name][0], req.encode('Shift_JIS', 'ignore')) header = response.splitlines() line = header.pop(0) if line: line = line.strip() if ' ' in line: saori_protocol, saori_statuscode = [x.strip() for x in line.split(' ', 1)] for line in header: line = line.strip() if not line: continue if ':' not in line: continue key, value = line.split(':', 1) key = key.strip() if key: saori_header.append(key) saori_value[key] = unicode(value, 'Shift_JIS', 'ignore').strip() for key in saori_value: if not key.startswith('Value'): continue try: i = int(key[5:]) except: continue name = ''.join((u'S', self.to_zenkaku(i))) self.saori_value[name] = saori_value[key] # overwrite if 'Result' in saori_value: return saori_value['Result'] else: return '' class SatoriSaoriLibrary(object): def __init__(self, saori, satori): self.saori_list = {} self.saori = saori self.satori = satori def load(self, name, top_dir): result = 0 if self.saori and name not in self.saori_list: module = self.saori.request(name) if module: self.saori_list[name] = module if name in self.saori_list: result = self.saori_list[name].load(top_dir) return result def unload(self): for key in self.saori_list.keys(): self.saori_list[key].unload() return None def request(self, name, req): result = '' # FIXME if name and name in self.saori_list: result = self.saori_list[name].request(req) return result def satori_open(top_dir): satori = Satori(top_dir) satori.load() return satori ### TEST ### def test(): logger = logging.getLogger() logger.setLevel(logging.DEBUG) # XXX if sys.argv[1] == 'ini': test_ini(sys.argv[2]) elif sys.argv[1] == 'parser': test_parser(sys.argv[2]) elif sys.argv[1] == 'interp': test_interp(sys.argv[2]) def test_ini(top_dir): dic_list = list_dict(top_dir) print 'number of files =', len(dic_list) for dic in dic_list: print dic def test_parser(path): parser = Parser() parser.read(path) for name, talk in parser.talk.items(): for node_list in talk: print u'*', name parser.print_nodelist(node_list) print for name, word in parser.word.items(): print u'@', name for node_list in word: print u'>>>', test_expand(node_list) parser.print_nodelist(node_list, 2) print def test_expand(node_list): buf = [] for node in node_list: if node[0] == NODE_TEXT: buf.extend(node[1]) elif node[0] == NODE_REF: buf.extend(test_expand(node[1])) else: raise RuntimeError, 'should not reach here' return ''.join(buf) def test_interp(top_dir): satori = Satori(top_dir) satori.load() while 1: try: name = unicode(raw_input(u'>>> '), 'utf-8', 'ignore') ## FIXME except: break if name.startswith(u'@'): print satori.getstring(name[2:]) elif name.startswith(u'*'): print satori.get_event_response(name[2:]) else: print satori.get_event_response(name) print satori.get_script(name) if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/dll/ssu.py000066400000000000000000000303541172114553600172700ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # ssu.py - a ssu compatible Saori module for ninix # Copyright (C) 2003-2012 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を使用) import codecs import logging import re from ninix.dll import SAORI class Saori(SAORI): def __init__(self): SAORI.__init__(self) self.function = {'is_empty': [self.ssu_is_empty, [0, 1]], 'is_digit': [self.ssu_is_digit, [1]], 'is_alpha': [self.ssu_is_alpha, [1]], 'iflist': [self.ssu_iflist, [None]], 'length': [self.ssu_length, [1]], 'zen2han': [self.ssu_zen2han, [1]], 'han2zen': [self.ssu_han2zen, [1]], 'kata2hira,': [self.ssu_kata2hira, [1]], 'hira2kata': [self.ssu_hira2kata, [1]], 'sprintf': [self.ssu_sprintf, [None]], 'calc': [self.ssu_calc, [1]], 'calc_float': [self.ssu_calc_float, [1]], 'compare_tail': [self.ssu_compare_tail, [2]], 'compare_head': [self.ssu_compare_head, [2]], 'compare': [self.ssu_compare, [2]], 'count': [self.ssu_count, [2]], 'erase_first': [self.ssu_erase_first, [2]], 'erase': [self.ssu_erase, [2]], 'replace': [self.ssu_replace, [3]], 'replace_first': [self.ssu_replace_first, [3]], 'split': [self.ssu_split, [1, 2, 3]], 'substr': [self.ssu_substr, [3]], 'nswitch': [self.ssu_nswitch, [None]], 'switch': [self.ssu_switch, [None]], 'if': [self.ssu_if, [2, 3]], 'unless': [self.ssu_unless, [2, 3]],} def request(self, req): req_type, argument, charset = self.evaluate_request(req) if not req_type: return self.RESPONSE[400] elif req_type == 'GET Version': return self.RESPONSE[204] elif req_type == 'EXECUTE': if argument[0] not in self.function: return self.RESPONSE[400] name = argument[0] argument = argument[1:] if self.function[name][1] == [None]: pass elif len(argument) not in self.function[name][1]: return self.RESPONSE[400] self.value = [] result = self.function[name][0](argument, charset) if result is not None and result != '': s = 'SAORI/1.0 200 OK\r\nResult: {0}\r\n'.format(result) if self.value: for i in range(len(self.value)): s = ''.join((s, 'Value{0:d}: {1}\r\n'.format(i, self.value[i]))) s = ''.join((s, 'Charset: {0}\r\n'.format(charset))) s = ''.join((s, '\r\n')) return s else: return self.RESPONSE[204] else: return self.RESPONSE[400] def ssu_is_empty(self, args, charset): return 1 if not args else 0 def ssu_is_digit(self, args, charset): s = unicode(args[0], charset, 'ignore') return 1 if s.isdigit() else 0 def ssu_is_alpha(self, args, charset): s = unicode(args[0], charset, 'ignore') return 1 if s.isalpha() else 0 def ssu_length(self, args, charset): s = unicode(args[0], charset, 'ignore') return len(s) def ssu_substr(self, args, charset): s = unicode(args[0], charset, 'ignore') if self.ssu_is_digit([args[1]], charset): start = int(self.ssu_zen2han([args[1]], charset)) if start > len(s): return '' else: return '' if len(args) == 2: end = len(s) elif len(args) == 3 and self.ssu_is_digit([args[2]], charset): end = start + int(self.ssu_zen2han([args[2]], charset)) else: return '' return s[start:end].encode(charset, 'ignore') def ssu_sprintf(self, args, charset): ## FIXME for i in range(1, len(args)): if self.ssu_is_digit([args[i]], charset): args[i] = int(self.ssu_zen2han([args[i]], charset)[0]) try: return args[0] % tuple(args[1:]) except: return '' 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', '.': '.', '+': '+', '-': '−',} def ssu_zen2han(self, args, charset): ## FIXME s = unicode(args[0], charset, 'ignore') buf = '' for i in range(len(s)): c = s[i].encode('utf-8', 'ignore') if c in self.ZEN: buf = ''.join((buf, unicode(self.ZEN[c], 'utf-8', 'ignore'))) else: buf = ''.join((buf, s[i])) return buf.encode(charset, 'ignore') def ssu_han2zen(self, args, charset): ## FIXME s = unicode(args[0], charset, 'ignore') buf = '' for i in range(len(s)): c = s[i].encode('utf-8', 'ignore') if c in self.HAN: buf = ''.join((buf, unicode(self.HAN[c], 'utf-8', 'ignore'))) else: buf = ''.join((buf, s[i])) return buf.encode(charset, 'ignore') re_condition = re.compile(r'(>|<|==|>=|<=|!=|>|<|>=|<=|!=|==)') def eval_condition(self, left, ope, right): if self.ssu_is_digit([left], 'utf-8') and \ self.ssu_is_digit([right], 'utf-8'): left = float(self.ssu_zen2han([left], 'utf-8')) right = float(self.ssu_zen2han([right], 'utf-8')) elif ope in ['>', '>', '>=', '>=', '<', '<', '<=', '<=']: left = len(left) right = len(right) result = False if ope in ['>', '>']: result = left > right elif ope in ['>=', '>=']: result = left >= right elif ope in ['<', '<']: result = left < right elif ope in ['<=', '<=']: result = left <= right elif ope in ['==', '==']: result = left == right elif ope in ['!=', '!=']: result = left != right else: pass # 'should not reach here' return result def ssu_if(self, args, charset): condition = unicode( args[0], charset, 'ignore').encode('utf-8', 'ignore') match = self.re_condition.search(condition) if match is not None: left = condition[:match.start()] ope = match.group(1) right = condition[match.end():] result = self.eval_condition(left, ope, right) if result: return args[1] return args[2] if len(args) == 3 else '' def ssu_unless(self, args, charset): condition = unicode( args[0], charset, 'ignore').encode('utf-8', 'ignore') match = self.re_condition.search(condition) if match is not None: left = condition[:match.start()] ope = match.group(1) right = condition[match.end():] result = self.eval_condition(left, ope, right) if not result: return args[1] return args[2] if len(args) == 3 else '' def ssu_iflist(self, args, charset): left = unicode(args[0], charset, 'ignore').encode('utf-8', 'ignore') i = 1 while 1: if len(args[i:]) < 2: break ope_right = unicode( args[i], charset, 'ignore').encode('utf-8', 'ignore') match = self.re_condition.search(ope_right) if match is not None: ope = match.group(1) right = ope_right[match.end():] result = self.eval_condition(left, ope, right) if result: return args[i + 1] i += 2 return '' def ssu_nswitch(self, args, charset): num = unicode(args[0], charset, 'ignore').encode('utf-8', 'ignore') if self.ssu_is_digit([num], 'utf-8'): num = int(self.ssu_zen2han([num], 'utf-8')) if 0 < num < len(args): return args[num] return '' def ssu_count(self, args, charset): return args[0].count(args[1]) def ssu_compare(self, args, charset): return 1 if args[0] == args[1] else 0 def ssu_compare_head(self, args, charset): s0 = args[0] s1 = args[1] return 1 if s1.startswith(s0) else 0 def ssu_compare_tail(self, args, charset): s0 = args[0] s1 = args[1] return 1 if s1.endswith(s0) else 0 def ssu_erase(self, args, charset): return args[0].replace(args[1], '') def ssu_erase_first(self, args, charset): return args[0].replace(args[1], '', 1) def ssu_replace(self, args, charset): return args[0].replace(args[1], args[2]) def ssu_replace_first(self, args, charset): return args[0].replace(args[1], args[2], 1) def ssu_split(self, args, charset): s0 = args[0] if len(args) >= 2: s1 = args[1] else: s1 = ' ' ## FIXME if len(args) == 3: num = unicode(args[0], charset, 'ignore').encode('utf-8', 'ignore') if self.ssu_is_digit([num], 'utf-8'): num = int(self.ssu_zen2han([num], 'utf-8')) value_list = s0.split(s1, num) else: value_list = s0.split(s1) self.value = value_list return len(value_list) def ssu_switch(self, args, charset): left = args[0] i = 1 while 1: if len(args[i:]) < 2: break right = args[i] if left == right: return args[i + 1] i += 2 return '' def ssu_kata2hira(self, args, charset): ## FIXME return '' def ssu_hira2kata(self, args, charset): ## FIXME return '' def ssu_calc(self, args, charset): ## FIXME return 0 def ssu_calc_float(self, args, charset): ## FIXME return 0 def evaluate_request(self, req): req_type = None argument = [] charset = 'Shift_JIS' # default header = req.splitlines() line = header.pop(0) if not line: return req_type, argument, charset line = line.strip() if not line: return req_type, argument, charset for request in ['EXECUTE', 'GET Version']: if line.startswith(request): req_type = request break for line in header: line = line.strip() if not line: continue if ':' not in line: continue key, value = [x.strip() for x in line.split(':', 1)] if key == 'Charset': charset = value try: codecs.lookup(charset) except: logging.warning('Unsupported charset {0}'.format(repr(charset))) if key.startswith('Argument'): ## FIXME argument.append(value) else: continue return req_type, argument, charset ninix-aya-4.3.9/lib/ninix/dll/textcopy.py000066400000000000000000000026401172114553600203320ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # textcopy.py - a TEXTCOPY compatible Saori module for ninix # Copyright (C) 2002-2012 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. # import gtk from ninix.dll import SAORI class Saori(SAORI): def __init__(self): SAORI.__init__(self) self.clipboard = None def setup(self): self.clipboard = gtk.Clipboard(selection='PRIMARY') return 1 def finalize(self): self.clipboard = None return 1 def execute(self, argument): if not argument or self.clipboard is None: return self.RESPONSE[400] text = argument[0].encode('utf-8') self.clipboard.set_text(text) if len(argument) >= 2 and argument[1] != 0: return 'SAORI/1.0 200 OK\r\n' \ 'Result: {0}\r\n\r\n'.format( argument[0].encode(self.charset, 'replace')) else: return self.RESPONSE[204] ninix-aya-4.3.9/lib/ninix/dll/win_dll.py000066400000000000000000000063361172114553600201110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # win_dll.py - a (Real) Windows DLL loader for ninix # Copyright (C) 2004 by linjian # Copyright (C) 2004-2012 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. # import os import sys import struct import logging import traceback from ctypes import cdll, c_char_p, c_long, create_string_buffer, byref import _ctypes ## FIXME ## DEFAULT_SCORE = 300 # 150 class Shiori(object): def __init__(self, dll_name): self.dll_name = dll_name self.pathdic = [] self.reqdic = [] self.dll = None def find(self, topdir, dll_name): bits = struct.calcsize('P') * 8 # XXX: cross-platform way to determine if os.name == 'nt' and bits == 32: return DEFAULT_SCORE else: return 0 def show_description(self): logging.info( 'Shiori: a (Real) Windows DLL loader for ninix\n' ' Copyright (C) 2004 by linjian\n' ' Copyright (C) 2004-2012 by Shyouzou Sugitani\n' ' Copyright (C) 2011 by henryhu') def load(self, topdir): self.dir = topdir try: self.dll = cdll.LoadLibrary(os.path.join(self.dir, self.dll_name)) except: logging.error('DLL({0}) load fail!'.format(self.dll_name)) # traceback.print_exc() self.dll = None if self.dll: if self.dir.endswith(os.sep): topdir = self.dir else: topdir = ''.join((self.dir, os.sep)) path = create_string_buffer(topdir) self.pathdic += [path] # so python would not free it # logging.debug(repr(self.pathdic)) # since DLL would free it (...) # we must not allow python to free it ret = self.dll.load(path, len(topdir)) # logging.debug('load result: {0:d}'.format(ret)) return ret else: return 0 def unload(self): if self.dll: self.dll.unload() try: _ctypes.FreeLibrary(self.dll._handle) except: pass self.dll = None def request(self, req_string): if self.dll: reqf = self.dll.request reqf.restype = c_char_p request = create_string_buffer(req_string) # since DLL may free it (...) # we must not allow python to free it self.reqdic += [request] # so python would not free it rlen = c_long(len(request)) # logging.debug('request: {0}'.format(req_string)) ret = reqf(request, byref(rlen)) # logging.debug('result len: {0:d}'.format(rlen.value)) # logging.debug(ret) return ret else: return '' # FIXME ninix-aya-4.3.9/lib/ninix/dll/wmove.py000066400000000000000000000213241172114553600176100ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # wmove.py - a wmove.dll compatible Saori module for ninix # Copyright (C) 2003-2012 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 import sys import os import glib from ninix.dll import SAORI import ninix.pix class Saori(SAORI): def __init__(self): SAORI.__init__(self) self.__sakura = None def need_ghost_backdoor(self, sakura): self.__sakura = sakura def load(self, dir=os.curdir): self.commands = [[], []] self.timeout_id = None self.dir = dir result = 0 if not self.__sakura: pass elif self.loaded: result = 2 else: self.sakura_name = self.__sakura.get_selfname() self.kero_name = self.__sakura.get_keroname() self.loaded = 1 result = 1 return result def finalize(self): if self.timeout_id: glib.source_remove(self.timeout_id) self.timeout_id = None self.commands = [[], []] self.sakura_name = '' self.kero_name = '' return 1 def __check_argument(self, argument): name = argument[0] result = 1 list_hwnd = [self.sakura_name, self.kero_name] ## FIXME: HWND if name in ['MOVE', 'MOVE_INSIDE', 'MOVETO', 'MOVETO_INSIDE']: if len(argument) != 4 or argument[1] not in list_hwnd: result = 0 elif name in ['ZMOVE', 'WAIT']: if len(argument) != 3 or argument[1] not in list_hwnd: result = 0 elif name in ['STANDBY', 'STANDBY_INSIDE']: if len(argument) != 6 or \ argument[1] not in list_hwnd or argument[2] not in list_hwnd: result = 0 elif name in ['GET_POSITION', 'CLEAR']: if len(argument) != 2 or argument[1] not in list_hwnd: result = 0 elif name == 'GET_DESKTOP_SIZE': if len(argument) != 1: result = 0 elif name == 'NOTIFY': if len(argument) < 3 or len(argument) > 8 or \ argument[1] not in list_hwnd: result = 0 return result def request(self, req): ## FIXME req_type, argument = self.evaluate_request(req) if not req_type: result = self.RESPONSE[400] elif req_type == 'GET Version': result = self.RESPONSE[204] elif req_type == 'EXECUTE': if not argument: result = self.RESPONSE[400] elif not self.__check_argument(argument): result = self.RESPONSE[400] else: name = argument[0] if name == 'GET_POSITION': if argument[1] == self.sakura_name: ## FIXME: HWND side = 0 elif argument[1] == self.kero_name: ## FIXME: HWND side = 1 else: return self.RESPONSE[400] try: x, y = self.__sakura.get_surface_position(side) w, h = self.__sakura.get_surface_size(side) result = 'SAORI/1.0 200 OK\r\n' \ 'Result: {0:d}\r\n' \ 'Value0: {1:d}\r\n' \ 'Value1: {2:d}\r\n' \ 'Value2: {3:d}\r\n\r\n'.format(x, x, x + w / 2, x + w) except: result = self.RESPONSE[500] elif name == 'GET_DESKTOP_SIZE': try: left, top, scrn_w, scrn_h = ninix.pix.get_workarea() result = 'SAORI/1.0 200 OK\r\n' \ 'Result: {0:d}\r\n' \ 'Value0: {1:d}\r\n' \ 'Value1: {2:d}\r\n\r\n'.format(scrn_w, scrn_w, scrn_h) except: result = self.RESPONSE[500] else: self.enqueue_commands(name, argument[1:]) if self.timeout_id is None: self.do_idle_tasks() result = self.RESPONSE[204] else: result = self.RESPONSE[400] return result def enqueue_commands(self, command, args): #assert command in ['MOVE', 'MOVE_INSIDE', 'MOVETO', 'MOVETO_INSIDE', # 'ZMOVE', 'WAIT', 'NOTIFY', # 'STANDBY', 'STANDBY_INSIDE', # 'CLEAR'] if args[0] == self.sakura_name: ## FIXME: HWND side = 0 elif args[0] == self.kero_name: ## FIXME: HWND side = 1 else: return # XXX if command == 'CLEAR': self.commands[side] = [] else: if command in ['STANDBY', 'STANDBY_INSIDE']: self.commands[0] = [] self.commands[1] = [] self.commands[side].append([command, args[1:]]) def do_idle_tasks(self): for side in [0, 1]: if self.commands[side]: command, args = self.commands[side].pop(0) if command in ['MOVE', 'MOVE_INSIDE']: x, y = self.__sakura.get_surface_position(side) vx = int(args[0]) speed = int(args[1]) if command == 'MOVE_INSIDE': w, h = self.__sakura.get_surface_size(side) left, top, scrn_w, scrn_h = ninix.pix.get_workarea() if vx < 0 and x + vx <0: vx = min(-x, 0) elif vx > 0 and x + vx + w > left + scrn_w: vx = max(left + scrn_w - w - x, 0) if abs(vx) > speed: if vx > 0: self.__sakura.set_surface_position( side, x + speed, y) self.commands[side].insert( 0, [command, [str(vx - speed), args[1]]]) elif vx < 0: self.__sakura.set_surface_position( side, x - speed, y) self.commands[side].insert( 0, [command, [str(vx + speed), args[1]]]) else: self.__sakura.set_surface_position(side, x + vx, y) elif command in ['MOVETO', 'MOVETO_INSIDE']: x, y = self.__sakura.get_surface_position(side) to = int(args[0]) speed = int(args[1]) if command == 'MOVETO_INSIDE': w, h = self.__sakura.get_surface_size(side) left, top, scrn_w, scrn_h = ninix.pix.get_workarea() if to < 0: to = 0 elif to > left + scrn_w - w: to = left + scrn_w - w if abs(to - x) > speed: if to - x > 0: self.__sakura.set_surface_position( side, x + speed, y) self.commands[side].insert(0, [command, args]) elif to - x < 0: self.__sakura.set_surface_position( side, x - speed, y) self.commands[side].insert(0, [command, args]) else: self.__sakura.set_surface_position(side, to, y) elif command in ['STANDBY', 'STANDBY_INSIDE']: pass ## FIXME elif command == 'ZMOVE': if args[0] == '1': self.__sakura.raise_surface(side) elif args[0] == '2': self.__sakura.lower_surface(side) else: pass elif command == 'WAIT': try: wait = int(args[0]) # ms except: wait = 0 if wait < 25: pass else: self.commands[side].insert(0, ['WAIT', str(wait - 20)]) elif command == 'NOTIFY': self.__sakura.notify_event(*args) if not self.commands[0] and not self.commands[1]: if self.timeout_id is not None: glib.source_remove(self.timeout_id) self.timeout_id = None else: if self.timeout_id is None: self.timeout_id = glib.timeout_add(20, self.do_idle_tasks) ninix-aya-4.3.9/lib/ninix/dll/yaya.py000066400000000000000000000113711172114553600174170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # yaya.py - a (Real) YAYA loader for ninix # Copyright (C) 2004 by linjian # Copyright (C) 2004-2012 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. # import os import sys import logging import traceback from ctypes import cdll, c_char_p, c_long, create_string_buffer, byref import _ctypes if os.name == 'posix': try: _yaya = cdll.LoadLibrary('libaya5.so') except: logging.error('yaya load fail!') # traceback.print_exc() _yaya = None else: _yaya = None class Shiori(object): def __init__(self, dll_name): self.dll_name = dll_name self.pathdic = [] self.reqdic = [] self.id = None def use_saori(self, saori): self.saori = saori def find(self, topdir, dll_name): result = 0 if _yaya: if os.path.isfile(os.path.join(topdir, 'yaya.txt')): result = 205 elif dll_name is not None and \ os.path.isfile(os.path.join(topdir, ''.join((dll_name[:-3], 'txt')))): result = 105 return result def show_description(self): logging.info( 'Shiori: a (Real) YAYA loader for ninix\n' ' Copyright (C) 2004 by linjian\n' ' Copyright (C) 2004-2012 by Shyouzou Sugitani\n' ' Copyright (C) 2011 by henryhu') def load(self, topdir): self.dir = topdir if _yaya: if self.dir.endswith(os.sep): topdir = self.dir else: topdir = ''.join((self.dir, os.sep)) path = create_string_buffer(topdir) self.pathdic += [path] # so python would not free it # logging.debug(repr(self.pathdic)) # since yaya would free it (...) # we must not allow python to free it self.id = _yaya.multi_load(path, len(topdir)) ret = 1 # logging.debug('load result: {0:d}'.format(ret)) ## FIXME: ctypes.CFUNCTYPE ##_yaya.setcallback(self.saori_exist, ## self.saori_load, ## self.saori_unload, ## self.saori_request) return ret else: return 0 def unload(self): if _yaya: _yaya.multi_unload(self.id) self.id = None def request(self, req_string): if _yaya: reqf = _yaya.multi_request reqf.restype = c_char_p request = create_string_buffer(req_string) # since yaya may free it (...) # we must not allow python to free it self.reqdic += [request] # so python would not free it rlen = c_long(len(request)) # logging.debug('request: {0}'.format(req_string)) ret = reqf(self.id, request, byref(rlen)) # logging.debug('result len: {0:d}'.format(rlen.value)) # logging.debug(ret) return ret else: return '' # FIXME ## def saori_exist(self, saori): ## module = self.saori.request(saori) ## if module: ## self.saori_list[saori] = [module, 0] ## return len(self.saori_list) ## else: ## return 0 ## ## def saori_load(self, saori, path): ## result = 0 ## if saori in self.saori_list and self.saori_list[saori][1] == 0: ## result = self.saori_list[saori][0].load(path) ## self.saori_list[saori][1] = result ## return result ## ## def saori_unload(self, saori): ## result = 0 ## if saori in self.saori_list and self.saori_list[saori][1] != 0: ## result = self.saori_list[saori][0].unload() ## self.saori_list[saori][1] = 0 ## return result ## ## def saori_request(self, saori, req): ## result = 'SAORI/1.0 500 Internal Server Error' ## if saori in self.saori_list: ## if self.saori_list[saori][1] == 0: ## head, tail = os.path.split(saori) ## self.saori_list[saori][1] = \ ## self.saori_list[saori][0].load(head) ## if self.saori_list[saori][1]: ## result = self.saori_list[saori][0].request(req) ## return result ninix-aya-4.3.9/lib/ninix/entry_db.py000066400000000000000000000030671172114553600175120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2004-2012 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. # import random class EntryDatabase(object): def __init__(self, db=None): self.__db = db or {} def add(self, key, script): entries = self.__db.get(key, []) entries.append(script) self.__db[key] = entries def get(self, key, default=None): entries = self.__db.get(key, [default]) return random.choice(entries) def is_empty(self): return not self.__db def test(): entry_db = EntryDatabase() print 'is_empty() =', entry_db.is_empty() entry_db.add('#temp0', '\hふーん。\e') entry_db.add('#temp0', '\hそうなのかぁ。\e') entry_db.add('#temp0', '\hほうほう。\e') entry_db.add('#temp2', '\hいい感じだね。\e') entry_db.add('#temp3', '\hなるほど。\e') for _ in range(5): print '#temp0', entry_db.get('#temp0') for key in ['#temp1', '#temp2', '#temp3']: print key, entry_db.get(key) print 'is_empty() =', entry_db.is_empty() if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/home.py000066400000000000000000000645101172114553600166340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import codecs import logging import os import re import sys from collections import OrderedDict import ninix.config import ninix.alias import ninix.dll PLUGIN_STANDARD = (2.0, 2.4) def get_ninix_home(): return os.path.join(os.path.expanduser('~'), '.ninix') def get_archive_dir(): return os.path.join(get_ninix_home(), 'archive') def get_pango_fontrc(): return os.path.join(get_ninix_home(), 'pango_fontrc') def get_preferences(): return os.path.join(get_ninix_home(), 'preferences') def get_normalized_path(path, encode=1): path = path.replace('\\', '/') if encode: path = path.encode('utf-8') if not os.path.isabs(path): # XXX path = path.lower() return os.path.normpath(path) def load_config(): if not os.path.exists(get_ninix_home()): return None ghosts = search_ghosts() balloons = search_balloons() plugins = search_plugins() nekoninni = search_nekoninni() katochan = search_katochan() kinoko = search_kinoko() return ghosts, balloons, plugins, nekoninni, katochan, kinoko def get_shiori(): table = {} shiori_lib = ninix.dll.Library('shiori', saori_lib=None) path = ninix.dll.get_path() for filename in os.listdir(path): if os.access(os.path.join(path, filename), os.R_OK): name = None basename, ext = os.path.splitext(filename) ext = ext.lower() if ext in ['.py', '.pyc']: name = basename if name and name not in table: shiori = shiori_lib.request(('', name)) if shiori: table[name] = shiori return table def search_ghosts(target=None): home_dir = get_ninix_home() ghosts = OrderedDict() if target: dirlist = [] dirlist.extend(target) else: try: dirlist = os.listdir(os.path.join(home_dir, 'ghost')) except OSError: dirlist = [] shiori_table = get_shiori() for subdir in dirlist: prefix = os.path.join(home_dir, 'ghost', subdir) ghost_dir = os.path.join(prefix, 'ghost', 'master') desc = read_descript_txt(ghost_dir) if desc is None: desc = ninix.config.null_config() 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.items(): score = int(shiori.find(ghost_dir, shiori_dll)) if score > candidate['score']: candidate['name'] = name candidate['score'] = score shell_name, surface_set = find_surface_set(prefix) if candidate['score'] == 0: continue shiori_name = candidate['name'] pos = 0 if desc.get('name') == 'default' else len(ghosts) 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) return ghosts def search_balloons(target=None): home_dir = get_ninix_home() balloons = OrderedDict() balloon_dir = os.path.join(home_dir, 'balloon') if target: dirlist = [] dirlist.extend(target) else: try: dirlist = os.listdir(balloon_dir) except OSError: dirlist = [] for subdir in dirlist: path = os.path.join(balloon_dir, subdir) if not os.path.isdir(path): continue desc = read_descript_txt(path) # REQUIRED if not desc: continue balloon_info = read_balloon_info(path) # REQUIRED if not balloon_info: continue if 'balloon_dir' in balloon_info: # XXX logging.warninig('Oops: balloon id confliction') continue else: balloon_info['balloon_dir'] = (subdir, ninix.config.null_config()) balloons[subdir] = (desc, balloon_info) return balloons def search_plugins(): home_dir = get_ninix_home() buf = [] plugin_dir = os.path.join(home_dir, 'plugin') try: dirlist = os.listdir(plugin_dir) except OSError: dirlist = [] for subdir in dirlist: plugin = read_plugin_txt(os.path.join(plugin_dir, subdir)) if plugin is None: continue buf.append(plugin) return buf def search_nekoninni(): home_dir = get_ninix_home() buf = [] skin_dir = os.path.join(home_dir, 'nekodorif/skin') try: dirlist = os.listdir(skin_dir) except OSError: dirlist = [] for subdir in dirlist: nekoninni = read_profile_txt(os.path.join(skin_dir, subdir)) if nekoninni is None: continue buf.append(nekoninni) return buf def search_katochan(): home_dir = get_ninix_home() buf = [] katochan_dir = os.path.join(home_dir, 'nekodorif/katochan') try: dirlist = os.listdir(katochan_dir) except OSError: dirlist = [] for subdir in dirlist: katochan = read_katochan_txt(os.path.join(katochan_dir, subdir)) if katochan is None: continue buf.append(katochan) return buf def search_kinoko(): home_dir = get_ninix_home() buf = [] kinoko_dir = os.path.join(home_dir, 'kinoko') try: dirlist = os.listdir(kinoko_dir) except OSError: dirlist = [] for subdir in dirlist: kinoko = read_kinoko_ini(os.path.join(kinoko_dir, subdir)) if kinoko is None: continue buf.append(kinoko) return buf def read_kinoko_ini(top_dir): path = os.path.join(top_dir, 'kinoko.ini') kinoko = {} kinoko['base'] = 'surface0.png' kinoko['animation'] = None kinoko['category'] = None kinoko['title'] = None kinoko['ghost'] = None kinoko['dir'] = top_dir kinoko['offsetx'] = 0 kinoko['offsety'] = 0 kinoko['ontop'] = 0 kinoko['baseposition'] = 0 kinoko['baseadjust'] = 0 kinoko['extractpath'] = None kinoko['nayuki'] = None if os.access(path, os.R_OK): with open(path) as f: line = f.readline() if not line.strip() or line.strip() != '[KINOKO]': return None lineno = 0 error = None for line in f: lineno += 1 if line.endswith(chr(0)): # XXX line = line[:-1] if not line.strip(): continue if '=' not in line: error = 'line {0:d}: syntax error'.format(lineno) break name, value = [x.strip() for x in line.split('=', 1)] if name in ['title', 'ghost', 'category']: kinoko[name] = unicode(value, 'Shift_JIS', 'ignore') elif name in ['offsetx', 'offsety']: kinoko[name] = int(value) elif name in ['base', 'animation', 'extractpath']: kinoko[name] = value elif name in ['ontop', 'baseposition', 'baseadjust']: kinoko[name] = int(value) if error: logging.error('Error: {0}\n{1} (skipped)'.format(error, path)) return None return kinoko if kinoko['title'] else None def read_profile_txt(top_dir): path = os.path.join(top_dir, 'profile.txt') name = None if os.access(path, os.R_OK): with open(path) as f: line = f.readline() if line: name = unicode(line.strip(), 'Shift_JIS', 'ignore') if name: return (name, top_dir) ## FIXME else: return None def read_katochan_txt(top_dir): path = os.path.join(top_dir, 'katochan.txt') katochan = {} katochan['dir'] = top_dir if os.access(path, os.R_OK): with open(path) as f: name = None lineno = 0 error = None for line in f: lineno += 1 if not line.strip(): continue if line.startswith('#'): name = line[1:].strip() continue elif not name: error = 'line {0:d}: syntax error'.format(lineno) break else: value = line.strip() if name in ['name', 'category']: katochan[name] = unicode(value, 'Shift_JIS', 'ignore') if name.startswith('before.script') or \ name.startswith('hit.script') or \ name.startswith('after.script') or \ name.startswith('end.script') or \ name.startswith('dodge.script'): ## FIXME: should be array katochan[name] = unicode(value, 'Shift_JIS', 'ignore') elif name in ['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']: katochan[name] = int(value) elif name in ['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']: katochan[name] = value else: name = None if error: logging.error('Error: {0}\n{1} (skipped)'.format(error, path)) return None return katochan if katochan['name'] else None def read_descript_txt(top_dir): path = os.path.join(top_dir, 'descript.txt') if os.access(path, os.R_OK): return ninix.config.create_from_file(path) return None def read_install_txt(top_dir): path = os.path.join(top_dir, 'install.txt') if os.access(path, os.R_OK): return ninix.config.create_from_file(path) return None def read_alias_txt(top_dir): path = os.path.join(top_dir, 'alias.txt') if os.access(path, os.R_OK): return ninix.alias.create_from_file(path) return None def find_makoto_dll(top_dir): return 1 if os.access(os.path.join(top_dir, 'makoto.dll'), os.R_OK) else 0 def find_surface_set(top_dir): desc = read_descript_txt(os.path.join(top_dir, 'ghost', 'master')) shell_name = desc.get('name') if desc else None if not shell_name: inst = read_install_txt(top_dir) if inst: shell_name = inst.get('name') surface_set = OrderedDict() shell_dir = os.path.join(top_dir, 'shell') for name, desc, subdir in find_surface_dir(shell_dir): surface_dir = os.path.join(shell_dir, subdir) surface_info, alias, tooltips = read_surface_info(surface_dir) if surface_info and \ 'surface0' in surface_info and 'surface10' in surface_info: if alias is None: alias = read_alias_txt(surface_dir) surface_set[subdir] = (name, surface_dir, desc, alias, surface_info, tooltips) return shell_name, surface_set def find_surface_dir(top_dir): buf = [] path = os.path.join(top_dir, 'surface.txt') if os.path.exists(path): config = ninix.config.create_from_file(path) for name, subdir in config.items(): subdir = subdir.lower() desc = read_descript_txt(os.path.join(top_dir, subdir)) if desc is None: desc = ninix.config.null_config() buf.append((name, desc, subdir)) else: try: dirlist = os.listdir(top_dir) except OSError: dirlist = [] for subdir in dirlist: desc = read_descript_txt(os.path.join(top_dir, subdir)) if desc is None: desc = ninix.config.null_config() name = desc.get('name', subdir) buf.append((name, desc, subdir)) return buf re_surface = re.compile('surface([0-9]+)\.(png|dgp|ddp)') def read_surface_info(surface_dir): surface = {} try: filelist = os.listdir(surface_dir) except OSError: filelist = [] filename_alias = {} path = os.path.join(surface_dir, 'alias.txt') if os.path.exists(path): dic = ninix.alias.create_from_file(path) for basename, alias in dic.items(): if basename.startswith('surface'): filename_alias[alias] = basename # find png image and associated configuration file for filename in filelist: basename, ext = os.path.splitext(filename) if basename in filename_alias: match = re_surface.match( ''.join((filename_alias[basename], ext))) else: match = re_surface.match(filename) if not match: continue img = os.path.join(surface_dir, filename) if not os.access(img, os.R_OK): continue key = ''.join(('surface', str(int(match.group(1))))) txt = os.path.join(surface_dir, ''.join((basename, 's.txt'))) if os.access(txt, os.R_OK): config = ninix.config.create_from_file(txt) else: config = ninix.config.null_config() txt = os.path.join(surface_dir, ''.join((basename, 'a.txt'))) if os.access(txt, os.R_OK): config.update(ninix.config.create_from_file(txt)) surface[key] = (img, config) # find surfaces.txt alias = None tooltips = {} for key, config in read_surfaces_txt(surface_dir): if key == '__alias__': alias = config elif key == '__tooltips__': tooltips = config elif key.startswith('surface'): try: img, prev_config = surface[key] prev_config.update(config) config = prev_config except KeyError: img = None surface[key] = (img, config) # find surface elements for key, (img, config) in surface.items(): for key, method, filename, x, y in list_surface_elements(config): filename = filename.lower() basename, ext = os.path.splitext(filename) if basename not in surface: surface[basename] = (os.path.join(surface_dir, filename), ninix.config.null_config()) return surface, alias, tooltips def read_surfaces_txt(surface_dir): config_list = [] path = os.path.join(surface_dir, 'surfaces.txt') try: with open(path) as f: alias_buffer = [] tooltips = {} charset = 'Shift_JIS' buf = [] key = None opened = False if f.read(3) == codecs.BOM_UTF8: charset = 'UTF-8' else: f.seek(0) # rewind for line in f: if line.startswith('#') or line.startswith('//'): continue if charset == 'Shift_JIS': # '\x81\x40': full-width space in Shift_JIS temp = line.replace('\x81\x40', '').strip() else: temp = line.strip() if not temp: continue if temp.startswith('charset'): try: charset = line.split(',', 1)[1].strip() except: pass continue if key is None: if temp.endswith('{'): key = temp[:-1] opened = True else: key = temp elif temp == '{': opened = True elif temp.endswith('}'): if temp[:-1]: buf.append(temp[:-1]) if not opened: logging.error( 'syntax error: unbalnced "}" in surfaces.txt.') if key in ['sakura.surface.alias', 'kero.surface.alias']: alias_buffer.append(key) alias_buffer.append('{') alias_buffer.extend(buf) alias_buffer.append('}') elif key.endswith('.tooltips'): try: key = key[:-9] except: pass value = {} for line in buf: line = line.split(',', 1) region, text = [unicode(s.strip(), charset, 'ignore') for s in line] value[region] = text tooltips[key] = value elif key.startswith('surface'): keys = key.split(',') for key in keys: if not key: continue if key.startswith('surface'): try: key = ''.join((key[:7], str(int(key[7:])))) except ValueError: pass else: try: key = ''.join(('surface', str(int(key)))) except ValueError: pass config_list.append((key, ninix.config.create_from_buffer(buf))) buf = [] key = None opened = False else: buf.append(temp) except IOError: return config_list if alias_buffer: config_list.append(('__alias__', ninix.alias.create_from_buffer(alias_buffer))) config_list.append(('__tooltips__', tooltips)) return config_list def list_surface_elements(config): buf = [] for n in range(256): key = ''.join(('element', str(n))) if key not in config: break spec = [value.strip() for value in config[key].split(',')] try: method, filename, x, y = spec x = int(x) y = int(y) except ValueError: logging.error( 'invalid element spec for {0}: {1}'.format(key, config[key])) continue buf.append((key, method, filename, x, y)) return buf re_balloon = re.compile('balloon([skc][0-9]+)\.(png)') re_annex = re.compile('(arrow[01]|sstp)\.(png)') def read_balloon_info(balloon_dir): balloon = {} try: filelist = os.listdir(balloon_dir) except OSError: filelist = [] for filename in filelist: match = re_balloon.match(filename) if not match: continue img = os.path.join(balloon_dir, filename) if match.group(2) != 'png' and \ os.access(''.join((img[-3:], 'png')), os.R_OK): continue if not os.access(img, os.R_OK): continue key = match.group(1) txt = os.path.join(balloon_dir, 'balloon{0}s.txt'.format(key)) if os.access(txt, os.R_OK): config = ninix.config.create_from_file(txt) else: config = ninix.config.null_config() balloon[key] = (img, config) for filename in filelist: match = re_annex.match(filename) if not match: continue img = os.path.join(balloon_dir, filename) if not os.access(img, os.R_OK): continue key = match.group(1) config = ninix.config.null_config() balloon[key] = (img, config) return balloon def read_plugin_txt(src_dir): path = os.path.join(src_dir, 'plugin.txt') try: with open(path) as f: charset = 'UTF-8' # default standard = 0.0 plugin_name = startup = None menu_items = [] error = None lineno = 0 if f.read(3) == codecs.BOM_UTF8: charset = 'UTF-8' # XXX else: f.seek(0) # rewind for line in f: lineno += 1 if not line.strip() or line.startswith('#'): continue if ':' not in line: error = 'line {0:d}: syntax error'.format(lineno) break name, value = [x.strip() for x in line.split(':', 1)] if name == 'charset': charset = value elif name == 'standard': standard = float(value) elif name == 'name': plugin_name = unicode(value, charset, 'ignore') elif name == 'startup': startup_list = value.split(',') # startup_list[0] = os.path.join(plugin_dir, startup_list[0]) if not os.path.exists(os.path.join(src_dir, startup_list[0])): error = 'line {0:d}: invalid program name'.format(lineno) break startup = startup_list elif name == 'menuitem': menuitem_list = unicode(value, charset, 'ignore').split(',') if len(menuitem_list) < 2: error = 'line {0:d}: syntax error'.format(lineno) break # menuitem_list[1] = os.path.join(plugin_dir, menuitem_list[1]) if not os.path.exists(os.path.join(src_dir, menuitem_list[1])): error = 'line {0:d}: invalid program name'.format(lineno) break menu_items.append((menuitem_list[0], menuitem_list[1:])) elif name == 'directory': plugin_dir = unicode(value, charset, 'ignore').encode('utf-8') # XXX else: error = 'line {0:d}: syntax error'.format(lineno) break else: if plugin_name is None: error = "the 'name' header field is required" elif not startup and not menu_items: error = "either 'startup' or 'menuitem' header field is required" elif standard < PLUGIN_STANDARD[0] or \ standard > PLUGIN_STANDARD[1]: error = "standard version mismatch" except IOError: return None if error: sys.stderr.write('Error: {0}\n{1} (skipped)\n'.format(error, path)) return None return plugin_name, plugin_dir, startup, menu_items ### TEST ### def test(): head, tail = os.path.split(sys.path[0]) sys.path[0] = head # XXX import locale locale.setlocale(locale.LC_ALL, '') config = load_config() if config is None: raise SystemExit, 'Home directory not found.\n' ghosts, balloons, plugins, nekoninni, katochan, kinoko = config # ghosts for key in ghosts: desc, shiori_dir, use_makoto, surface_set, prefix, shiori_dll, shiori_name = ghosts[key] print 'GHOST', '=' * 50 print prefix print str(desc) print shiori_dir print shiori_dll print shiori_name print 'use_makoto =', use_makoto if surface_set: for name, surface_dir, desc, alias, surface, tooltips in surface_set.values(): print '-' * 50 print 'surface:', name.encode('utf-8', 'ignore') print str(desc) for k, v in surface.items(): print k, '=', v[0] print str(v[1]) if alias: buf = [] for k, v in alias.items(): if k in ['sakura.surface.alias', 'kero.surface.alias']: print ''.join((k, ':')) for alias_id, alias_list in v.items(): print alias_id, \ ''.join(('= [', ', '.join(alias_list), ']')) print else: buf.append((k, v)) if buf: print 'filename alias:' for k, v in buf: print k, '=', v print # balloons for key in balloons: desc, balloon = balloons[key] print 'BALLOON', '=' * 50 print str(desc) for k, v in balloon.items(): print k, '=', v[0] print str(v[1]) # plugins for plugin_name, plugin_dir, startup, menu_items in plugins: print 'PLUGIN', '=' * 50 print 'name =', plugin_name.encode('utf-8', 'ignore') if startup: print 'startup =', ''.join(('["', '", "'.join(startup), '"]')) for label, argv in menu_items: print "menuitem '{0}' =".format(label.encode('utf-8', 'ignore')), print ''.join(('["', '", "'.join(argv), '"]')) ## FIXME # kinoko # nekoninni # katochan if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/install.py000066400000000000000000000612311172114553600173470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # install.py - an installer module for ninix # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import distutils.dir_util import os import shutil import stat import sys import logging import tempfile import urllib import zipfile import gtk import ninix.home import ninix.version class URLopener(urllib.FancyURLopener): version = 'ninix-aya/{0}'.format(ninix.version.VERSION) urllib._urlopener = URLopener() class InstallError(Exception): pass def fatal(error): logging.error(error) # XXX raise InstallError, error class Installer(object): def __init__(self): self.fs_encoding = 'mbcs' if os.name == 'nt' else 'utf-8' self.dialog = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO) self.select_dialog = gtk.Dialog( buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) ls = gtk.ListStore(int, str) tv = gtk.TreeView(model=ls) tv.set_rules_hint(True) renderer = gtk.CellRendererText() col0 = gtk.TreeViewColumn('No.',renderer,text=0) col1 = gtk.TreeViewColumn('Path',renderer,text=1) tv.append_column(col0) tv.append_column(col1) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(tv) sw.show_all() # XXX self.treeview = tv label = gtk.Label( 'Multiple candidates found.\n' 'Select the path name of the supplement target.') ## FIXME ##label.set_use_markup(True) self.select_dialog.vbox.pack_start(label) label.show() self.select_dialog.vbox.pack_start(sw) # XXX self.select_dialog.set_title('Select the target') ## FIXME def check_archive(self, filename): # check archive format basename, ext = os.path.splitext(filename) ext = ext.lower() if ext in ['.nar', '.zip']: pass else: fatal('unknown archive format') def extract_files(self, filename): # extract files from the archive tmpdir = tempfile.mkdtemp('ninix-aya') shutil.rmtree(tmpdir) # XXX try: os.makedirs(tmpdir) except: fatal('cannot make temporary directory') url = None if filename.startswith('http:') or filename.startswith('ftp:'): url = filename filename = self.download(filename, tmpdir) if filename is None: shutil.rmtree(tmpdir) fatal('cannot download the archive file') try: with zipfile.ZipFile(filename) as zf: for name in zf.namelist(): uname = name.decode('cp932') # XXX path = os.path.join(tmpdir, uname.encode(self.fs_encoding)) dname, fname = os.path.split(path) if not os.path.exists(dname): os.makedirs(dname) if not fname: # directory continue buf = zf.read(name) with open(path, 'wb') as of: of.write(buf) except: shutil.rmtree(tmpdir) fatal('cannot extract files from the archive') for (dirpath, dirnames, filenames) in os.walk(tmpdir): for name in dirnames: path = os.path.join(dirpath, name) st_mode = os.stat(path).st_mode os.chmod(path, st_mode | stat.S_IWUSR | stat.S_IRUSR | stat.S_IXUSR) for name in filenames: path = os.path.join(dirpath, name) st_mode = os.stat(path).st_mode os.chmod(path, st_mode | stat.S_IWUSR | stat.S_IRUSR) self.rename_files(tmpdir) return tmpdir def get_file_type(self, tmpdir): # check the file type inst = ninix.home.read_install_txt(tmpdir) if not inst: if os.path.exists(os.path.join(tmpdir, 'kinoko.ini')): filetype = 'kinoko' elif os.path.exists(os.path.join(tmpdir, 'plugin.txt')): filetype = 'plugin' else: fatal('cannot read install.txt from the archive') else: filetype = inst.get('type') if filetype == 'ghost': if not os.path.exists(os.path.join(tmpdir, 'ghost', 'master')): filetype = 'ghost.inverse' elif filetype == 'ghost with balloon': filetype = 'ghost.inverse' elif filetype in ['shell', 'shell with balloon', 'supplement']: if 'accept' in inst: filetype = 'supplement' else: filetype = 'shell.inverse' elif filetype == 'balloon': pass elif filetype == 'plugin': pass elif filetype == 'skin': filetype = 'nekoninni' elif filetype == 'katochan': pass elif filetype == 'kinoko': pass else: fatal('unsupported file type({0})'.format(filetype)) if filetype in ['shell.inverse', 'ghost.inverse']: fatal('unsupported file type({0})'.format(filetype)) return filetype def install(self, filename, homedir): self.check_archive(filename) tmpdir = self.extract_files(filename) filetype = self.get_file_type(tmpdir) try: func = getattr(self, 'install_{0}'.format(filetype)) target_dir = func(filename, tmpdir, homedir) finally: shutil.rmtree(tmpdir) return filetype, target_dir def download(self, url, basedir): #logging.debug('downloading {0}'.format(url)) try: ifile = urllib.urlopen(url) except IOError: return None headers = ifile.info() #if 'content-length' in headers: # logging.debug( # '(size = {0} bytes)'.format(headers.get('content-length'))) arcdir = ninix.home.get_archive_dir() if not os.path.exists(arcdir): os.makedirs(arcdir) basedir = arcdir filename = os.path.join(basedir, os.path.basename(url)) try: with open(filename, 'wb') as ofile: while 1: data = ifile.read(4096) if not data: break ofile.write(data) except IOError: return None ifile.close() # check the format of the downloaded file self.check_archive(filename) ## FIXME try: zf = zipfile.ZipFile(filename) except: return None test_zip = zf.testzip() zf.close() return None if test_zip is not None else filename def rename_files(self, basedir): if os.name == 'nt': # XXX return for filename in os.listdir(basedir): filename2 = filename.lower() path = os.path.join(basedir, filename2) if filename != filename2: os.rename(os.path.join(basedir, filename), path) if os.path.isdir(path): self.rename_files(path) def list_all_directories(self, top, basedir): dirlist = [] for path in os.listdir(os.path.join(top, basedir)): if os.path.isdir(os.path.join(top, basedir, path)): dirlist.extend(self.list_all_directories( top, os.path.join(basedir, path))) dirlist.append(os.path.join(basedir, path)) return dirlist def remove_files_and_dirs(self, target_dir, mask): path = os.path.abspath(target_dir) if not os.path.isdir(path): return os.path.walk(path, self.remove_files, mask) dirlist = self.list_all_directories(path, '') dirlist.sort() dirlist.reverse() for name in dirlist: current_path = os.path.join(path, name) if os.path.isdir(current_path): head, tail = os.path.split(current_path) if tail not in mask and not os.listdir(current_path): shutil.rmtree(current_path) def remove_files(self, mask, top_dir, filelist): for name in filelist: path = os.path.join(top_dir, name) if os.path.isdir(path) or name in mask: pass else: os.remove(path) def lower_files(self, top_dir): if os.name == 'nt': # XXX return n = 0 for filename in os.listdir(top_dir): filename2 = filename.lower() path = os.path.join(top_dir, filename2) if filename != filename2: os.rename(os.path.join(top_dir, filename), path) logging.info( 'renamed {0}'.format(os.path.join(top_dir, filename))) n += 1 if os.path.isdir(path): n += lower_files(path) return n def confirm(self, message): self.dialog.set_markup(message) response = self.dialog.run() self.dialog.hide() return response == gtk.RESPONSE_YES def confirm_overwrite(self, path, type_string): if os.name == 'nt': path = unicode(path, 'mbcs', 'ignore').encode('utf-8') return self.confirm('Overwrite "{0}"({1})?'.format(path, type_string)) def confirm_removal(self, path, type_string): if os.name == 'nt': path = unicode(path, 'mbcs', 'ignore').encode('utf-8') return self.confirm('Remove "{0}"({1})?'.format(path, type_string)) def select(self, candidates): assert len(candidates) >= 1 if len(candidates) == 1: return candidates[0] ls = self.treeview.get_model() ls.clear() for i, item in enumerate(candidates): ls.append((i, item)) ts = self.treeview.get_selection() ts.select_iter(ls.get_iter_first()) response = self.select_dialog.run() self.select_dialog.hide() if response != gtk.RESPONSE_ACCEPT: return None model, it = ts.get_selected() return model.get_value(it, 1) def install_ghost(self, archive, tmpdir, homedir): # find install.txt inst = ninix.home.read_install_txt(tmpdir) if inst is None: fatal('install.txt not found') target_dir = inst.get('directory') if target_dir is None: fatal('"directory" not found in install.txt') target_dir = target_dir.encode(self.fs_encoding) prefix = os.path.join(homedir, 'ghost', target_dir) ghost_src = os.path.join(tmpdir, 'ghost', 'master') shell_src = os.path.join(tmpdir, 'shell') ghost_dst = os.path.join(prefix, 'ghost', 'master') shell_dst = os.path.join(prefix, 'shell') filelist = [] ##filelist.append((os.path.join(tmpdir, 'install.txt'), ## os.path.join(prefix, 'install.txt'))) # XXX readme_txt = os.path.join(tmpdir, 'readme.txt') if os.path.exists(readme_txt): filelist.append((readme_txt, os.path.join(prefix, 'readme.txt'))) thumbnail_png = os.path.join(tmpdir, 'thumbnail.png') thumbnail_pnr = os.path.join(tmpdir, 'thumbnail.pnr') if os.path.exists(thumbnail_png): filelist.append((thumbnail_png, os.path.join(prefix, 'thumbnail.png'))) elif os.path.exists(thumbnail_pnr): filelist.append((thumbnail_pnr, os.path.join(prefix, 'thumbnail.pnr'))) for path in self.list_all_files(ghost_src, ''): filelist.append((os.path.join(ghost_src, path), os.path.join(ghost_dst, path))) # find shell for path in self.list_all_files(shell_src, ''): filelist.append((os.path.join(shell_src, path), os.path.join(shell_dst, path))) # find balloon balloon_dir = inst and inst.get('balloon.directory') if balloon_dir: balloon_dir = ninix.home.get_normalized_path(balloon_dir) balloon_dst = os.path.join(homedir, 'balloon', balloon_dir) balloon_src = inst and inst.get('balloon.source.directory') if balloon_src: balloon_src = ninix.home.get_normalized_path(balloon_src) else: balloon_src = balloon_dir if os.path.exists(balloon_dst) and \ not self. confirm_removal(balloon_dst, 'balloon'): pass # don't install balloon else: if os.path.exists(balloon_dst): # uninstall older versions of the balloon self.remove_files_and_dirs(balloon_dst, []) balloon_list = [] for path in self.list_all_files(os.path.join(tmpdir, balloon_src), ''): balloon_list.append((os.path.join(tmpdir, balloon_src, path), os.path.join(balloon_dst, path))) self.install_files(balloon_list) if os.path.exists(prefix): inst_dst = ninix.home.read_install_txt(prefix) if inst.get_with_type('refresh', int, 0): # uninstall older versions of the ghost if self.confirm_removal(prefix, 'ghost'): mask = [ninix.home.get_normalized_path(path) for path in \ inst.get('refreshundeletemask', '').split(':')] mask.append('HISTORY') self.remove_files_and_dirs(prefix, mask) else: return else: if not self.confirm_overwrite(prefix, 'ghost'): return # install files logging.info('installing {0} (ghost)'.format(archive)) self.install_files(filelist) # create SETTINGS path = os.path.join(prefix, 'SETTINGS') if not os.path.exists(path): try: with open(path, 'w') as f: if balloon_dir: f.write('balloon_directory, {0}\n'.format(balloon_dir)) except IOError as e: code, message = e.args logging.error('cannot write {0}'.format(path)) return target_dir def install_supplement(self, archive, tmpdir, homedir): inst = ninix.home.read_install_txt(tmpdir) if inst and 'accept' in inst: logging.info('searching supplement target ...') candidates = [] try: dirlist = os.listdir(os.path.join(homedir, 'ghost')) except OSError: dirlist = [] for dirname in dirlist: path = os.path.join(homedir, 'ghost', dirname) if os.path.exists(os.path.join(path, 'shell', 'surface.txt')): continue # ghost.inverse(obsolete) desc = ninix.home.read_descript_txt( os.path.join(path, 'ghost', 'master')) if desc and desc.get('sakura.name') == inst.get('accept'): candidates.append(dirname) if not candidates: logging.info('not found') else: target = self.select(candidates) if target is None: return path = os.path.join(homedir, 'ghost', target) if 'directory' in inst: if inst.get('type') == 'shell': path = os.path.join(path, 'shell', inst['directory']) else: if 'type' not in inst: logging.error('supplement type not specified') else: logging.error('unsupported supplement type: {0}'.format(inst['type'])) return logging.info('found') if not os.path.exists(path): os.makedirs(path) os.remove(os.path.join(tmpdir, 'install.txt')) distutils.dir_util.copy_tree(tmpdir, path) return target def install_balloon(self, archive, srcdir, homedir): # find install.txt inst = ninix.home.read_install_txt(srcdir) if inst is None: fatal('install.txt not found') target_dir = inst.get('directory') if target_dir is None: fatal('"directory" not found in install.txt') target_dir = target_dir.encode(self.fs_encoding) dstdir = os.path.join(homedir, 'balloon', target_dir) filelist = [] for path in self.list_all_files(srcdir, ''): filelist.append((os.path.join(srcdir, path), os.path.join(dstdir, path))) ##filelist.append((os.path.join(srcdir, 'install.txt'), ## os.path.join(dstdir, 'install.txt'))) if os.path.exists(dstdir): inst_dst = ninix.home.read_install_txt(dstdir) if inst.get_with_type('refresh', int, 0): # uninstall older versions of the balloon if self.confirm_removal(dstdir, 'balloon'): mask = [ninix.home.get_normalized_path(path) for path in \ inst.get('refreshundeletemask', '').split(':')] self.remove_files_and_dirs(dstdir, mask) else: return else: if not self.confirm_overwrite(dstdir, 'balloon'): return # install files logging.info('installing {0} (balloon)'.format(archive)) self.install_files(filelist) return target_dir def uninstall_plugin(self, homedir, name): try: dirlist = os.listdir(os.path.join(homedir, 'plugin')) except OSError: return for subdir in dirlist: path = os.path.join(homedir, 'plugin', subdir) plugin = ninix.home.read_plugin_txt(path) if plugin is None: continue plugin_name, plugin_dir, startup, menu_items = plugin if plugin_name == name: plugin_dir = os.path.join(homedir, 'plugin', subdir) if self.confirm_removal(plugin_dir, 'plugin'): shutil.rmtree(plugin_dir) def install_plugin(self, archive, srcdir, homedir): filelist = [] ## dstdir = os.path.join(homedir, 'plugin', os.path.basename(archive)[:-4]) # find plugin.txt plugin = ninix.home.read_plugin_txt(srcdir) if plugin is None: fatal('failed to read plugin.txt') plugin_name, plugin_dir, startup, menu_items = plugin # find files for filename in os.listdir(srcdir): path = os.path.join(srcdir, filename) if os.path.isfile(path): # filelist.append((path, os.path.join(dstdir, filename))) filelist.append((path, os.path.join(homedir, 'plugin', plugin_dir, filename))) # uninstall older versions of the plugin self.uninstall_plugin(homedir, plugin_name) # install files print 'installing {0} (plugin)'.format(archive) self.install_files(filelist) return plugin_dir def uninstall_kinoko(self, homedir, name): try: dirlist = os.listdir(os.path.join(homedir, 'kinoko')) except OSError: return for subdir in dirlist: path = os.path.join(homedir, 'kinoko', subdir) kinoko = ninix.home.read_kinoko_ini(path) if kinoko is None: continue kinoko_name = kinoko['title'] if kinoko_name == name: kinoko_dir = os.path.join(homedir, 'kinoko', subdir) if self.confirm_removal(kinoko_dir, 'kinoko'): shutil.rmtree(kinoko_dir) def install_kinoko(self, archive, srcdir, homedir): # find kinoko.ini kinoko = ninix.home.read_kinoko_ini(srcdir) if kinoko is None: fatal('failed to read kinoko.ini') kinoko_name = kinoko['title'] if kinoko['extractpath'] is not None: dstdir = os.path.join( homedir, 'kinoko', kinoko['extractpath'].encode(self.fs_encoding, 'ignore')) else: dstdir = os.path.join( homedir, 'kinoko', os.path.basename(archive)[:-4]) # find files filelist = [] for filename in os.listdir(srcdir): path = os.path.join(srcdir, filename) if os.path.isfile(path): filelist.append((path, os.path.join(dstdir, filename))) # uninstall older versions of the kinoko self.uninstall_kinoko(homedir, kinoko_name) # install files logging.info('installing {0} (kinoko)'.format(archive)) self.install_files(filelist) return dstdir def uninstall_nekoninni(self, homedir, dir): nekoninni_dir = os.path.join(homedir, 'nekodorif', 'skin', dir) if not os.path.exists(nekoninni_dir): return if self.confirm_removal(nekoninni_dir, 'nekodorif skin'): shutil.rmtree(nekoninni_dir) def install_nekoninni(self, archive, srcdir, homedir): # find install.txt inst = ninix.home.read_install_txt(srcdir) if inst is None: fatal('install.txt not found') target_dir = inst.get('directory') if target_dir is None: fatal('"directory" not found in install.txt') target_dir = target_dir.encode(self.fs_encoding) dstdir = os.path.join(homedir, 'nekodorif', 'skin', target_dir) # find files filelist = [] for filename in os.listdir(srcdir): path = os.path.join(srcdir, filename) if os.path.isfile(path): filelist.append((path, os.path.join(dstdir, filename))) # uninstall older versions of the skin self.uninstall_nekoninni(homedir, target_dir) # install files logging.info('installing {0} (nekodorif skin)'.format(archive)) self.install_files(filelist) return target_dir def uninstall_katochan(self, homedir, target_dir): katochan_dir = os.path.join( homedir, 'nekodorif', 'katochan', target_dir) if not os.path.exists(katochan_dir): return if self.confirm_removal(katochan_dir, 'nekodorif katochan'): shutil.rmtree(katochan_dir) def install_katochan(self, archive, srcdir, homedir): # find install.txt inst = ninix.home.read_install_txt(srcdir) if inst is None: fatal('install.txt not found') target_dir = inst.get('directory') if target_dir is None: fatal('"directory" not found in install.txt') target_dir = target_dir.encode(self.fs_encoding) dstdir = os.path.join(homedir, 'nekodorif', 'katochan', target_dir) # find files filelist = [] for filename in os.listdir(srcdir): path = os.path.join(srcdir, filename) if os.path.isfile(path): filelist.append((path, os.path.join(dstdir, filename))) # uninstall older versions of the skin self.uninstall_katochan(homedir, target_dir) # install files logging.info('installing {0} (nekodorif katochan)'.format(archive)) self.install_files(filelist) return target_dir def list_all_files(self, top, target_dir): filelist = [] for path in os.listdir(os.path.join(top, target_dir)): if os.path.isdir(os.path.join(top, target_dir, path)): filelist.extend(self.list_all_files( top, os.path.join(target_dir, path))) else: filelist.append(os.path.join(target_dir, path)) return filelist def install_files(self, filelist): for from_path, to_path in filelist: dirname, filename = os.path.split(to_path) if not os.path.exists(dirname): os.makedirs(dirname) shutil.copy(from_path, to_path) ninix-aya-4.3.9/lib/ninix/keymap.py000066400000000000000000000161541172114553600171730ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2002 by Tamito KAJIYAMA # Copyright (C) 2003-2012 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. # import gtk keymap_old = { gtk.keysyms.BackSpace: 'back', gtk.keysyms.Tab: 'tab', gtk.keysyms.KP_Tab: 'tab', gtk.keysyms.Clear: 'clear', gtk.keysyms.Return: 'return', gtk.keysyms.KP_Enter: 'return', gtk.keysyms.Menu: '', gtk.keysyms.Pause: 'pause', gtk.keysyms.Kanji: '', gtk.keysyms.Escape: 'escape', gtk.keysyms.Henkan: '', gtk.keysyms.Muhenkan: '', gtk.keysyms.space: 'space', gtk.keysyms.Prior: 'prior', gtk.keysyms.Next: 'next', gtk.keysyms.End: 'end', gtk.keysyms.Home: 'home', gtk.keysyms.Left: 'left', gtk.keysyms.Up: 'up', gtk.keysyms.Right: 'right', gtk.keysyms.Down: 'down', gtk.keysyms.Select: '', gtk.keysyms.Print: '', gtk.keysyms.Execute: '', gtk.keysyms.Insert: '', gtk.keysyms.Delete: 'delete', gtk.keysyms.Help: '', gtk.keysyms._0: '0', gtk.keysyms._1: '1', gtk.keysyms._2: '2', gtk.keysyms._3: '3', gtk.keysyms._4: '4', gtk.keysyms._5: '5', gtk.keysyms._6: '6', gtk.keysyms._7: '7', gtk.keysyms._8: '8', gtk.keysyms._9: '9', gtk.keysyms.a: 'a', gtk.keysyms.b: 'b', gtk.keysyms.c: 'c', gtk.keysyms.d: 'd', gtk.keysyms.e: 'e', gtk.keysyms.f: 'f', gtk.keysyms.g: 'g', gtk.keysyms.h: 'h', gtk.keysyms.i: 'i', gtk.keysyms.j: 'j', gtk.keysyms.k: 'k', gtk.keysyms.l: 'l', gtk.keysyms.m: 'm', gtk.keysyms.n: 'n', gtk.keysyms.o: 'o', gtk.keysyms.p: 'p', gtk.keysyms.q: 'q', gtk.keysyms.r: 'r', gtk.keysyms.s: 's', gtk.keysyms.t: 't', gtk.keysyms.u: 'u', gtk.keysyms.v: 'v', gtk.keysyms.w: 'w', gtk.keysyms.x: 'x', gtk.keysyms.y: 'y', gtk.keysyms.z: 'z', gtk.keysyms.KP_0: '0', gtk.keysyms.KP_1: '1', gtk.keysyms.KP_2: '2', gtk.keysyms.KP_3: '3', gtk.keysyms.KP_4: '4', gtk.keysyms.KP_5: '5', gtk.keysyms.KP_6: '6', gtk.keysyms.KP_7: '7', gtk.keysyms.KP_8: '8', gtk.keysyms.KP_9: '9', gtk.keysyms.KP_Multiply: '*', gtk.keysyms.KP_Add: '+', gtk.keysyms.KP_Separator: '', gtk.keysyms.KP_Subtract: '-', gtk.keysyms.KP_Decimal: '', gtk.keysyms.KP_Divide: '/', gtk.keysyms.F1: 'f1', gtk.keysyms.F2: 'f2', gtk.keysyms.F3: 'f3', gtk.keysyms.F4: 'f4', gtk.keysyms.F5: 'f5', gtk.keysyms.F6: 'f6', gtk.keysyms.F7: 'f7', gtk.keysyms.F8: 'f8', gtk.keysyms.F9: 'f9', gtk.keysyms.F10: 'f10', gtk.keysyms.F11: 'f11', gtk.keysyms.F12: 'f12', gtk.keysyms.F13: 'f13', gtk.keysyms.F14: 'f14', gtk.keysyms.F15: 'f15', gtk.keysyms.F16: 'f16', gtk.keysyms.F17: 'f17', gtk.keysyms.F18: 'f18', gtk.keysyms.F19: 'f19', gtk.keysyms.F20: 'f20', gtk.keysyms.F21: 'f21', gtk.keysyms.F22: 'f22', gtk.keysyms.F23: 'f23', gtk.keysyms.F24: 'f24', gtk.keysyms.Num_Lock: '', gtk.keysyms.Scroll_Lock: '', gtk.keysyms.Shift_L: '', gtk.keysyms.Shift_R: '', gtk.keysyms.Control_L: '', gtk.keysyms.Control_R: '', } keymap_new = { gtk.keysyms.BackSpace: '8', gtk.keysyms.Tab: '9', gtk.keysyms.KP_Tab: '9', gtk.keysyms.Clear: '12', gtk.keysyms.Return: '13', gtk.keysyms.KP_Enter: '13', gtk.keysyms.Menu: '18', gtk.keysyms.Pause: '19', gtk.keysyms.Kanji: '25', gtk.keysyms.Escape: '27', gtk.keysyms.Henkan: '28', gtk.keysyms.Muhenkan: '29', gtk.keysyms.space: '32', gtk.keysyms.Prior: '33', gtk.keysyms.Next: '34', gtk.keysyms.End: '35', gtk.keysyms.Home: '36', gtk.keysyms.Left: '37', gtk.keysyms.Up: '38', gtk.keysyms.Right: '39', gtk.keysyms.Down: '40', gtk.keysyms.Select: '41', gtk.keysyms.Print: '42', gtk.keysyms.Execute: '43', gtk.keysyms.Insert: '45', gtk.keysyms.Delete: '46', gtk.keysyms.Help: '47', gtk.keysyms._0: '48', gtk.keysyms._1: '49', gtk.keysyms._2: '50', gtk.keysyms._3: '51', gtk.keysyms._4: '52', gtk.keysyms._5: '53', gtk.keysyms._6: '54', gtk.keysyms._7: '55', gtk.keysyms._8: '56', gtk.keysyms._9: '57', gtk.keysyms.a: '65', gtk.keysyms.b: '66', gtk.keysyms.c: '67', gtk.keysyms.d: '68', gtk.keysyms.e: '69', gtk.keysyms.f: '70', gtk.keysyms.g: '71', gtk.keysyms.h: '72', gtk.keysyms.i: '73', gtk.keysyms.j: '74', gtk.keysyms.k: '75', gtk.keysyms.l: '76', gtk.keysyms.m: '77', gtk.keysyms.n: '78', gtk.keysyms.o: '79', gtk.keysyms.p: '80', gtk.keysyms.q: '81', gtk.keysyms.r: '82', gtk.keysyms.s: '83', gtk.keysyms.t: '84', gtk.keysyms.u: '85', gtk.keysyms.v: '86', gtk.keysyms.w: '87', gtk.keysyms.x: '88', gtk.keysyms.y: '89', gtk.keysyms.z: '90', gtk.keysyms.KP_0: '96', gtk.keysyms.KP_1: '97', gtk.keysyms.KP_2: '98', gtk.keysyms.KP_3: '99', gtk.keysyms.KP_4: '100', gtk.keysyms.KP_5: '101', gtk.keysyms.KP_6: '102', gtk.keysyms.KP_7: '103', gtk.keysyms.KP_8: '104', gtk.keysyms.KP_9: '105', gtk.keysyms.KP_Multiply: '106', gtk.keysyms.KP_Add: '107', gtk.keysyms.KP_Separator: '108', gtk.keysyms.KP_Subtract: '109', gtk.keysyms.KP_Decimal: '110', gtk.keysyms.KP_Divide: '111', gtk.keysyms.F1: '112', gtk.keysyms.F2: '113', gtk.keysyms.F3: '114', gtk.keysyms.F4: '115', gtk.keysyms.F5: '116', gtk.keysyms.F6: '117', gtk.keysyms.F7: '118', gtk.keysyms.F8: '119', gtk.keysyms.F9: '120', gtk.keysyms.F10: '121', gtk.keysyms.F11: '122', gtk.keysyms.F12: '123', gtk.keysyms.F13: '124', gtk.keysyms.F14: '125', gtk.keysyms.F15: '126', gtk.keysyms.F16: '127', gtk.keysyms.F17: '128', gtk.keysyms.F18: '129', gtk.keysyms.F19: '130', gtk.keysyms.F20: '131', gtk.keysyms.F21: '132', gtk.keysyms.F22: '133', gtk.keysyms.F23: '134', gtk.keysyms.F24: '135', gtk.keysyms.Num_Lock: '144', gtk.keysyms.Scroll_Lock: '145', gtk.keysyms.Shift_L: '160', gtk.keysyms.Shift_R: '161', gtk.keysyms.Control_L: '162', gtk.keysyms.Control_R: '163', } def test(): def key_press(widget, event): try: print keymap_old[event.keyval], \ keymap_new[event.keyval], '({0:d})'.format(event.keyval) except KeyError: if event.string: print repr(event.string), '({0:d})'.format(event.keyval) else: print 'unknown keyval:', event.keyval win = gtk.Window() win.set_events(gtk.gdk.KEY_PRESS_MASK) win.connect('destroy', gtk.main_quit) win.connect('key_press_event', key_press) win.show() gtk.main() if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/kinoko.py000066400000000000000000000402531172114553600171740ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2004-2012 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 # - 透明度の設定 import os import logging import gtk import glib import cairo import ninix.config import ninix.seriko import ninix.pix class Menu(object): def __init__(self, accelgroup): self.request_parent = lambda *a: None # dummy ui_info = ''' ''' self.__menu_list = { 'settings': [('Settings', None, ('Settings...(_O)'), None, '', lambda *a: self.request_parent('NOTIFY', 'edit_preferences')), '/ui/popup/Settings'], 'skin': [('Skin', None, _('Skin(_K)'), None), None, '/ui/popup/Skin'], 'exit': [('Exit', None, _('Exit(_Q)'), None, '', lambda *a: self.request_parent('NOTIFY', 'close')), '/ui/popup/Exit'], } self.__skin_list = None actions = gtk.ActionGroup('Actions') entry = [value[0] for value in self.__menu_list.values()] actions.add_actions(tuple(entry)) ui_manager = gtk.UIManager() ui_manager.insert_action_group(actions, 0) ui_manager.add_ui_from_string(ui_info) self.__popup_menu = ui_manager.get_widget('/ui/popup') for key in self.__menu_list: path = self.__menu_list[key][-1] self.__menu_list[key][1] = ui_manager.get_widget(path) def set_responsible(self, request_method): self.request_parent = request_method def popup(self, button): skin_list = self.request_parent('GET', 'get_skin_list') self.__set_skin_menu(skin_list) self.__popup_menu.popup( None, None, None, button, gtk.get_current_event_time()) def __set_skin_menu(self, list): ## FIXME key = 'skin' if list: menu = gtk.Menu() for skin in list: item = gtk.MenuItem(skin['title']) item.connect( 'activate', lambda a, k: self.request_parent('NOTIFY', 'select_skin', k), (skin)) menu.add(item) item.show() self.__menu_list[key][1].set_submenu(menu) menu.show() self.__menu_list[key][1].show() else: self.__menu_list[key][1].hide() class Nayuki(object): def __init__(self): pass class Kinoko(object): def __init__(self, skin_list): self.skin_list = skin_list self.skin = None def edit_preferences(self): pass def finalize(self): self.__running = 0 self.target.detach_observer(self) if self.skin is not None: self.skin.destroy() def observer_update(self, event, args): if self.skin is None: return if event in ['set position', 'set surface']: self.skin.set_position() self.skin.show() elif event == 'set scale': scale = self.target.get_surface_scale() self.skin.set_scale(scale) elif event == 'hide': side = args if side == 0: # sakura side self.skin.hide() elif event == 'iconified': self.skin.hide() elif event == 'deiconified': self.skin.show() elif event == 'finalize': self.finalize() elif event == 'move surface': side, xoffset, yoffset = args if side == 0: # sakura side self.skin.set_position(xoffset, yoffset) elif event == 'raise': side = args if side == 0: # sakura side self.skin.set_position() ## FIXME else: ##logging.debug('OBSERVER(kinoko): ignore - {0}'.format(event)) pass def load_skin(self): scale = self.target.get_surface_scale() self.skin = Skin(self.accelgroup) self.skin.set_responsible(self.handle_request) self.skin.load(self.data, scale) def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { 'get_target_window': lambda *a: self.target.surface.window[0].window, # XXX 'get_kinoko_position': self.target.get_kinoko_position, } handler = handlers.get(event, getattr(self, event, lambda *a: None)) ## FIXME result = handler(*arglist, **argdict) if event_type == 'GET': return result def load(self, data, target): self.data = data self.target = target self.target.attach_observer(self) self.accelgroup = gtk.AccelGroup() self.load_skin() if self.skin is None: return 0 else: self.send_event('OnKinokoObjectCreate') self.__running = 1 glib.timeout_add(10, self.do_idle_tasks) # 10[ms] return 1 def do_idle_tasks(self): return True if self.__running else False def close(self): self.finalize() self.send_event('OnKinokoObjectDestroy') def send_event(self, event): if event not in ['OnKinokoObjectCreate', 'OnKinokoObjectDestroy', 'OnKinokoObjectChanging', 'OnKinokoObjectChanged', 'OnKinokoObjectInstalled']: ## 'OnBatteryLow', 'OnBatteryCritical', ## 'OnSysResourceLow', 'OnSysResourceCritical' return args = (self.data['title'], self.data['ghost'], self.data['category']) self.target.notify_event(event, *args) def get_skin_list(self): return self.skin_list def select_skin(self, args): self.send_event('OnKinokoObjectChanging') self.skin.destroy() self.data = args self.load_skin() if self.skin is None: return 0 else: self.send_event('OnKinokoObjectChanged') return 1 class Skin(object): def __init__(self, accelgroup): self.frame_buffer = [] self.accelgroup = accelgroup self.request_parent = lambda *a: None # dummy self.__menu = Menu(self.accelgroup) self.__menu.set_responsible(self.handle_request) def set_responsible(self, request_method): self.request_parent = request_method def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { } handler = handlers.get(event, getattr(self, event, None)) if handler is None: result = self.request_parent( event_type, event, *arglist, **argdict) else: result = handler(*arglist, **argdict) if event_type == 'GET': return result def load(self, data, scale): self.data = data self.__scale = scale self.__shown = False self.surface_id = 0 # dummy self.window = ninix.pix.TransparentWindow() self.window.set_focus_on_map(False) ##self.window.set_title(''.join(('surface.', name))) self.window.set_decorated(False) self.window.set_resizable(False) self.window.set_skip_taskbar_hint(True) self.window.connect('delete_event', self.delete) self.window.realize() self.window.window.set_back_pixmap(None, False) self.window.add_accel_group(self.accelgroup) ## FIXME if self.data['animation'] is not None: path = os.path.join(self.data['dir'], self.data['animation']) actors = {'': ninix.seriko.get_actors(ninix.config.create_from_file(path))} else: base, ext = os.path.splitext(self.data['base']) path = os.path.join(self.data['dir'], ''.join((base, 'a.txt'))) if os.path.exists(path): actors = {'': ninix.seriko.get_actors(ninix.config.create_from_file(path))} else: actors = {'': []} self.seriko = ninix.seriko.Controler( actors) self.seriko.set_responsible(self.handle_request) path = os.path.join(self.data['dir'], self.data['base']) try: self.pixbuf = ninix.pix.create_pixbuf_from_file(path) w = max(8, self.pixbuf.get_width() * self.__scale / 100) h = max(8, self.pixbuf.get_height() * self.__scale / 100) surface_pixbuf = self.pixbuf.scale_simple( w, h, gtk.gdk.INTERP_BILINEAR) mask_pixmap = gtk.gdk.Pixmap(None, w, h, 1) surface_pixbuf.render_threshold_alpha( mask_pixmap, 0, 0, 0, 0, w, h, 1) except: ## FIXME self.request_parent('NOTIFY', 'close') return self.w, self.h = w, h self.current_surface_pixbuf = surface_pixbuf self.window.mask = mask_pixmap self.darea = gtk.DrawingArea() self.darea.set_events(gtk.gdk.EXPOSURE_MASK| gtk.gdk.BUTTON_PRESS_MASK| gtk.gdk.BUTTON_RELEASE_MASK| gtk.gdk.POINTER_MOTION_MASK| gtk.gdk.LEAVE_NOTIFY_MASK) self.darea.connect('button_press_event', self.button_press) self.darea.connect('button_release_event', self.button_release) self.darea.connect('motion_notify_event', self.motion_notify) self.darea.connect('leave_notify_event', self.leave_notify) self.darea.connect('expose_event', self.redraw) self.darea.set_size_request(self.w, self.h) self.darea.show() self.window.add(self.darea) self.set_position() target_window = self.request_parent('GET', 'get_target_window') if self.data['ontop']: self.window.set_transient_for(target_window) else: target_window.set_transient_for(self.window) self.show() self.seriko.reset(self, '') # XXX self.seriko.start(self) self.seriko.invoke_kinoko(self) def get_preference(self, name): # dummy if name == 'animation_quality': return 1.0 else: return None def show(self): if not self.__shown: self.window.show() self.__shown = True def hide(self): if self.__shown: self.window.hide() self.__shown = False def append_actor(self, frame, actor): self.seriko.append_actor(frame, actor) def set_position(self, xoffset=0, yoffset=0): base_x, base_y = self.request_parent( 'GET', 'get_kinoko_position', self.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)][self.data['baseadjust']] offsetx = self.data['offsetx'] * self.__scale / 100 offsety = self.data['offsety'] * self.__scale / 100 self.x = base_x - int(self.w * a) + offsetx + xoffset self.y = base_y - int(self.h * b) + offsety + yoffset self.window.resize_move(self.x, self.y) def set_scale(self, scale): self.__scale = scale self.reset_surface() self.set_position() def get_surface(self): ## FIXME return None def redraw(self, darea, event): cr = darea.window.cairo_create() cr.save() cr.set_operator(cairo.OPERATOR_CLEAR) cr.paint() cr.restore() cr.set_source_pixbuf(self.current_surface_pixbuf, 0, 0) ##cr.paint_with_alpha(self.__alpha_channel) cr.paint() del cr def get_pixbuf(self, surface_id): path = os.path.join(self.data['dir'], ''.join(('surface', str(surface_id), '.png'))) if os.path.exists(path): pixbuf = ninix.pix.create_pixbuf_from_file(path) else: pixbuf = None return pixbuf def create_surface_pixbuf(self, surface_id=None): if surface_id is not None and surface_id != '': surface_pixbuf = self.get_pixbuf(surface_id) else: surface_pixbuf = self.pixbuf.copy() return surface_pixbuf def update_frame_buffer(self): surface_pixbuf = self.create_surface_pixbuf(self.seriko.base_id) assert surface_pixbuf is not None # draw overlays for pixbuf_id, x, y in self.seriko.iter_overlays(): try: pixbuf = self.get_pixbuf(pixbuf_id) w = pixbuf.get_width() h = pixbuf.get_height() except: continue # overlay surface pixbuf sw = surface_pixbuf.get_width() sh = surface_pixbuf.get_height() if x + w > sw: w = sw - x if y + h > sh: h = sh - y if x < 0: dest_x = 0 w += x else: dest_x = x if y < 0: dest_y = 0 h += y else: dest_y = y pixbuf.composite(surface_pixbuf, dest_x, dest_y, w, h, x, y, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) w = max(8, surface_pixbuf.get_width() * self.__scale / 100) h = max(8, surface_pixbuf.get_height() * self.__scale / 100) surface_pixbuf = surface_pixbuf.scale_simple( w, h, gtk.gdk.INTERP_BILINEAR) mask_pixmap = gtk.gdk.Pixmap(None, w, h, 1) surface_pixbuf.render_threshold_alpha( mask_pixmap, 0, 0, 0, 0, w, h, 1) #self.darea.queue_draw_area(0, 0, w, h) self.window.mask = mask_pixmap self.current_surface_pixbuf = surface_pixbuf self.darea.queue_draw() def terminate(self): self.seriko.terminate(self) def add_overlay(self, actor, surface_id, x, y): self.seriko.add_overlay(self, actor, surface_id, x, y) def remove_overlay(self, actor): self.seriko.remove_overlay(actor) def move_surface(self, xoffset, yoffset): self.window.resize_move(self.x + xoffset, self.y + yoffset) def reset_surface(self): ## FIXME self.seriko.reset(self, '') # XXX path = os.path.join(self.data['dir'], self.data['base']) w, h = ninix.pix.get_png_size(path) w = max(8, w * self.__scale / 100) h = max(8, h * self.__scale / 100) self.w, self.h = w, h # XXX self.darea.set_size_request(w, h) self.window.queue_resize() self.seriko.start(self) self.seriko.invoke_kinoko(self) def set_surface(self, surface_id, restart=1): ## FIXME self.pixbuf = self.get_pixbuf(surface_id) def invoke(self, actor_id, update=0): self.seriko.invoke(self, actor_id, update) def delete(self, widget, event): self.request_parent('NOTIFY', 'close') def destroy(self): self.seriko.destroy() self.window.destroy() def button_press(self, widget, event): ## FIXME self.x_root = event.x_root self.y_root = event.y_root if event.type == gtk.gdk.BUTTON_PRESS: click = 1 else: click = 2 button = event.button if button == 3 and click == 1: self.__menu.popup(button) return True def button_release(self, widget, event): ## FIXME pass def motion_notify(self, widget, event): ## FIXME pass def leave_notify(self, widget, event): ## FIXME pass ninix-aya-4.3.9/lib/ninix/lock.py000066400000000000000000000027351172114553600166350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2011, 2012 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. # import os if os.name == 'posix': import fcntl def lockfile(fileobj): fcntl.lockf(fileobj.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) def unlockfile(fileobj): fcntl.lockf(fileobj.fileno(), fcntl.LOCK_UN) elif os.name == 'nt': import win32con import win32file import pywintypes def lockfile(fileobj): overlapped = pywintypes.OVERLAPPED() handle = win32file._get_osfhandle(fileobj.fileno()) win32file.LockFileEx( handle, win32con.LOCKFILE_EXCLUSIVE_LOCK | win32con.LOCKFILE_FAIL_IMMEDIATELY, 0, -0x7fff0000, overlapped) # XXX: eq. 0xffff0000 def unlockfile(fileobj): overlapped = pywintypes.OVERLAPPED() handle = win32file._get_osfhandle(fileobj.fileno()) win32file.UnlockFileEx(handle, 0, -0x7fff0000, overlapped) # XXX: eq. 0xffff0000 else: def lockfile(fileobj): pass ## FIXME def unlockfile(fileobj): pass ## FIXME ninix-aya-4.3.9/lib/ninix/makoto.py000066400000000000000000000072611172114553600171760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2004-2012 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. # import random def execute(s): buf = [] i = 0 j = len(s) while i < j: i, text = expand(s, i) buf.append(text) return ''.join(buf) def expand(s, start): segments = [] repeat_count = None validity = 0 i = start j = len(s) buf = [] while i < j: if s[i] == '\\': if i + 1 < j and s[i + 1] in ['(', '|', ')']: buf.append(s[i + 1]) else: buf.append(s[i:i + 2]) i += 2 elif s[i] == '(': if validity == 0: segments.append(''.join(buf)) buf = [] i += 1 else: i, text = expand(s, i) buf.append(text) validity = 1 elif s[i] == '|' and validity > 0: segments.append(''.join(buf)) buf = [] i += 1 elif s[i] == ')' and validity > 0: segments.append(''.join(buf)) i += 1 if i < j and s[i] in '123456789': repeat_count = int(s[i]) i += 1 validity = 2 break else: buf.append(s[i]) i += 1 if validity == 2: expanded = random.choice(segments[1:]) if repeat_count: expanded = expanded * random.randint(0, repeat_count) return i, segments[0] + expanded elif validity == 0: return j, ''.join(buf) else: return j, s[start:] def test(verbose=0): for test, expected in [(r'a(1)b', ['a1b']), (r'a(1|2)b', ['a1b', 'a2b']), (r'a(1)2b', ['ab', 'a1b', 'a11b']), (r'a(1|2)1b', ['ab', 'a1b', 'a2b']), (r'(1|2)(a|b)', ['1a', '1b', '2a', '2b']), (r'((1|2)|(a|b))', ['1', '2', 'a', 'b']), (r'()', ['']), (r'()2', ['']), (r'a()b', ['ab']), (r'a()2b', ['ab']), (r'a\(1\|2\)b', ['a(1|2)b']), (r'\((1|2)\)', ['(1)', '(2)']), (r'\(1)', ['(1)']), (r'a|b', ['a|b']), # errornous cases (r'(1', ['(1']), (r'(1\)', ['(1\)']), (r'(1|2', ['(1|2']), (r'(1|2\)', ['(1|2\)']), (r'(1|2)(a|b', ['1(a|b', '2(a|b']), (r'((1|2)|(a|b)', ['((1|2)|(a|b)']), ]: result = execute(test) if verbose: print repr(test), '=>', repr(result), '...', try: if expected is None: assert(result == test) else: assert(result in expected) if verbose: print 'OK' except AssertionError: if verbose: print 'NG' raise if __name__ == '__main__': print 'testing...' for i in range(1000): test() test(1) ninix-aya-4.3.9/lib/ninix/menu.py000066400000000000000000000675641172114553600166640ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2003-2012 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. # import os import re import gtk import ninix.pix class Menu(object): def __init__(self): self.request_parent = lambda *a: None # dummy ui_info = ''' ''' self.__menu_list = { 'Portal': { 'entry': ('Portal', None, _('Portal sites(_P)'), None), 'visible': 1}, 'Recommend': { 'entry': ('Recommend', None, _('Recommend sites(_R)'), None), 'visible': 1}, 'Options': { 'entry': ('Options', None, _('Options(_F)'), None), 'visible': 1}, 'Options/Update': { 'entry': ('Update', None, _('Network Update(_U)'), None, '', lambda *a: self.request_parent('NOTIFY', 'network_update')), 'visible': 1}, 'Options/Vanish': { 'entry': ('Vanish', None, _('Vanish(_F)'), None, '', lambda *a: self.request_parent('NOTIFY', 'vanish')), 'visible': 1}, 'Options/Preferences': { 'entry': ('Preferences', None, _('Preferences...(_O)'), None, '', lambda *a: self.request_parent('NOTIFY', 'edit_preferences')), 'visible': 1}, 'Options/Console': { 'entry': ('Console', None, _('Console(_C)'), None, '', lambda *a: self.request_parent('NOTIFY', 'open_console')), 'visible': 1}, 'Options/Manager': { 'entry': ('Manager', None, _('Ghost Manager(_M)'), None, '', lambda *a: self.request_parent('NOTIFY', 'open_ghost_manager')), 'visible': 1}, 'Information': { 'entry': ('Information', None, _('Information(_I)'), None), 'visible': 1}, 'Information/Usage': { 'entry': ('Usage', None, _('Usage graph(_A)'), None, '', lambda *a: self.request_parent('NOTIFY', 'show_usage')), 'visible': 1}, 'Information/Version': { 'entry': ('Version', None, _('Version(_V)'), None, '', lambda *a: self.request_parent('NOTIFY', 'about')), 'visible': 1}, 'Close': { 'entry': ('Close', None, _('Close(_W)'), None, '', lambda *a: self.request_parent('NOTIFY', 'close_sakura')), 'visible': 1}, 'Quit': { 'entry': ('Quit', None, _('Quit(_Q)'), None, '', lambda *a: self.request_parent('NOTIFY', 'close_all')), 'visible': 1}, 'Change': { 'entry': ('Change', None, _('Change(_G)'), None), 'visible': 1}, 'Summon': { 'entry': ('Summon', None, _('Summon(_X)'), None), 'visible': 1}, 'Shell': { 'entry': ('Shell', None, _('Shell(_S)'), None), 'visible': 1}, 'Balloon': { 'entry': ('Balloon', None, _('Balloon(_B)'), None), 'visible': 1}, 'Costume': { 'entry': ('Costume', None, _('Costume(_C)'), None), 'visible': 1}, 'Stick': { 'entry': ('Stick', None, _('Stick(_Y)'), None, '', lambda *a: self.request_parent('NOTIFY', 'stick_window'), False), 'visible': 1}, 'Nekodorif': { 'entry': ('Nekodorif', None, _('Nekodorif(_N)'), None), 'visible': 1}, 'Kinoko': { 'entry': ('Kinoko', None, _('Kinoko(_K)'), None), 'visible': 1}, 'Plugin': { 'entry': ('Plugin', None, _('Plugin(_P)'), None), 'visible': 1}, } self.__pixmap = None self.__pixmap_with_sidebar = None self.__pixmap_prelight = None self.__pixmap_prelight_with_sidebar = None self.__fontcolor = {'normal': '#000000', 'prelight': '#ffffff'} ## FIXME self.__sidebar_width = 0 actions = gtk.ActionGroup('Actions') entry = [value['entry'] for key, value in self.__menu_list.items() \ if key != 'Stick'] actions.add_actions(tuple(entry)) actions.add_toggle_actions(tuple([self.__menu_list['Stick']['entry']])) self.ui_manager = gtk.UIManager() self.ui_manager.insert_action_group(actions, 0) self.ui_manager.add_ui_from_string(ui_info) self.__popup_menu = self.ui_manager.get_widget('/popup') def set_responsible(self, request_method): self.request_parent = request_method def set_fontcolor(self, background, foreground): self.__fontcolor['normal'] = '#{0:02x}{1:02x}{2:02x}'.format(*background) self.__fontcolor['prelight'] = '#{0:02x}{1:02x}{2:02x}'.format(*foreground) def set_pixmap(self, path_background, path_sidebar, path_foreground): pixbuf = None if path_background and os.path.exists(path_background): try: pixbuf = ninix.pix.create_pixbuf_from_file( path_background, is_pnr=False) except: pixbuf = None if pixbuf is None: self.__pixmap = None self.__pixmap_with_sidebar = None self.__pixmap_prelight = None self.__pixmap_prelight_with_sidebar = None self.__sidebar_width = 0 return pixmap, mask = pixbuf.render_pixmap_and_mask(255) self.__pixmap = pixmap pixbuf_sidebar = None if path_sidebar and os.path.exists(path_sidebar): try: pixbuf_sidebar = ninix.pix.create_pixbuf_from_file( path_sidebar, is_pnr=False) except: pixbuf_sidebar = None if pixbuf_sidebar: width = pixbuf.get_width() height = pixbuf.get_height() sidebar_width = pixbuf_sidebar.get_width() sidebar_height = pixbuf_sidebar.get_height() new_pixbuf = gtk.gdk.Pixbuf( pixbuf.get_colorspace(), False, pixbuf.get_bits_per_sample(), width + sidebar_width, max(height, sidebar_height)) pixbuf.composite( new_pixbuf, 0, 0, new_pixbuf.get_width(), new_pixbuf.get_height(), width - 1, height - 1, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) pixbuf_sidebar.composite( new_pixbuf, 0, 0, sidebar_width, sidebar_height, 0, 0, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) pixbuf.composite( new_pixbuf, sidebar_width, 0, width, height, 0, 0, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) self.__sidebar_width = sidebar_width pixmap, mask = new_pixbuf.render_pixmap_and_mask(255) else: self.__sidebar_width = 0 self.__pixmap_with_sidebar = pixmap pixbuf = None if path_foreground and os.path.exists(path_foreground): try: pixbuf = ninix.pix.create_pixbuf_from_file( path_foreground, is_pnr=False) except: pixbuf = None if pixbuf is None: return pixmap, mask = pixbuf.render_pixmap_and_mask(255) self.__pixmap_prelight = pixmap if pixbuf_sidebar: width = pixbuf.get_width() height = pixbuf.get_height() sidebar_width = pixbuf_sidebar.get_width() sidebar_height = pixbuf_sidebar.get_height() new_pixbuf = gtk.gdk.Pixbuf( pixbuf.get_colorspace(), False, pixbuf.get_bits_per_sample(), width + sidebar_width, max(height, sidebar_height)) pixbuf.composite( new_pixbuf, 0, 0, new_pixbuf.get_width(), new_pixbuf.get_height(), width - 1, height - 1, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) pixbuf_sidebar.composite( new_pixbuf, 0, 0, sidebar_width, sidebar_height, 0, 0, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) pixbuf.composite( new_pixbuf, sidebar_width, 0, width, height, 0, 0, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) pixmap, mask = new_pixbuf.render_pixmap_and_mask(255) self.__pixmap_prelight_with_sidebar = pixmap def __set_mayuna_menu(self, side): if len(self.__mayuna_menu) > side and \ self.__mayuna_menu[side] is not None: menuitem = self.ui_manager.get_widget( ''.join(('/popup/', 'Costume'))) menuitem.set_submenu(self.__mayuna_menu[side]) self.__set_visible('Costume', 1) else: self.__set_visible('Costume', 0) def create_mayuna_menu(self, mayuna_menu): self.__mayuna_menu = [] for side, index in [('sakura', 0), ('kero', 1)]: if mayuna_menu[side]: self.__mayuna_menu.append(gtk.Menu()) item = gtk.TearoffMenuItem() item.show() self.__mayuna_menu[index].append(item) for j in range(len(mayuna_menu[side])): key, name, state = mayuna_menu[side][j] if key != '-': item = gtk.CheckMenuItem(name) item.set_name('popup menu item') item.set_active(bool(state)) item.connect( 'activate', lambda a, k: self.request_parent( 'NOTIFY', 'toggle_bind', k), (index, key)) else: item = gtk.SeparatorMenuItem() item.show() self.__mayuna_menu[index].append(item) else: self.__mayuna_menu.append(None) __re_shortcut = re.compile(r'&(?=[\x21-\x7e])') def __modify_shortcut(self, caption): return self.__re_shortcut.sub('_', caption) __re_mnemonic = re.compile(r'\(_.\)|_') def __cut_mnemonic(self, caption): return self.__re_mnemonic.sub('', caption) __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)', [[], None]), 'Recommend': ([['sakura.recommendbuttoncaption', 'recommendrootbutton.caption'], ['kero.recommendbuttoncaption']], '(_R)', [[], []]), } def __update_ui(self, side): for key in self.__ui: assert key in self.__menu_list if side > 1: if key in ['Options/Update', 'Options/Vanish']: name_list = self.__ui[key][0][1] # same as 'kero' elif key == 'Portal': name_list = [] # same as 'kero' elif key == 'Recommend': name_list = ['char{0:d}.recommendbuttoncaption'.format(side)] else: name_list = self.__ui[key][0][side] if name_list: # caption for name in name_list: caption = self.request_parent('GET', 'getstring', name) if caption: break if caption: caption = self.__modify_shortcut(caption) if caption == self.__cut_mnemonic(caption): caption = ''.join((caption, self.__ui[key][1])) self.__set_caption(key, caption) if side > 1: name_list = self.__ui[key][2][1] # same as 'kero' else: name_list = self.__ui[key][2][side] if name_list: # visible for name in name_list: visible = self.request_parent('GET', 'getstring', name) if visible is not None: break if visible == '0': self.__set_visible(key, 0) else: self.__set_visible(key, 1) elif name_list is None: self.__set_visible(key, 0) def set_submenu_back_pixmap(self, submenu): item_list = submenu.get_children() if item_list: for item in item_list: style = item.style.copy() style.bg_pixmap[gtk.STATE_PRELIGHT] = self.__pixmap_prelight item.set_style(style) submenu = item.get_submenu() if submenu: style = submenu.style.copy() style.bg_pixmap[gtk.STATE_NORMAL] = self.__pixmap submenu.set_style(style) self.set_submenu_back_pixmap(submenu) def set_submenu_fontcolor(self, submenu): for item in submenu.get_children(): # reset label children = item.get_children() if children: label = children[0] style = label.style.copy() cmap = label.get_colormap() colour = cmap.alloc_color(self.__fontcolor['normal']) style.fg[gtk.STATE_NORMAL] = colour colour = cmap.alloc_color(self.__fontcolor['prelight']) style.fg[gtk.STATE_PRELIGHT] = colour label.set_style(style) submenu = item.get_submenu() if submenu: self.set_submenu_fontcolor(submenu) def popup(self, button, side): if side > 1: string = 'char{0:d}'.format(side) else: assert side in [0, 1] ## FIXME string = ['sakura', 'kero'][side] string = ''.join((string, '.popupmenu.visible')) if self.request_parent('GET', 'getstring', string) == '0': return self.__update_ui(side) if side == 0: portal = self.request_parent( 'GET', 'getstring', 'sakura.portalsites') else: portal = None self.__set_portal_menu(side, portal) if side > 1: string = 'char{0:d}'.format(side) else: assert side in [0, 1] ## FIXME string = ['sakura', 'kero'][side] string = ''.join((string, '.recommendsites')) recommend = self.request_parent('GET', 'getstring', string) self.__set_recommend_menu(recommend) self.__set_ghost_menu() self.__set_shell_menu() self.__set_balloon_menu() self.__set_plugin_menu() self.__set_mayuna_menu(side) self.__set_nekodorif_menu() self.__set_kinoko_menu() style = self.__popup_menu.style.copy() style.bg_pixmap[gtk.STATE_NORMAL] = self.__pixmap_with_sidebar self.__popup_menu.set_style(style) for key in self.__menu_list: item = self.ui_manager.get_widget(''.join(('/popup/', key))) visible = self.__menu_list[key]['visible'] if item: if visible: if not key.startswith('Options/'): # XXX style = item.style.copy() style.bg_pixmap[gtk.STATE_PRELIGHT] = self.__pixmap_prelight_with_sidebar item.set_style(style) label = item.get_children()[0] style = label.style.copy() cmap = label.get_colormap() colour = cmap.alloc_color(self.__fontcolor['normal']) style.fg[gtk.STATE_NORMAL] = colour colour = cmap.alloc_color(self.__fontcolor['prelight']) style.fg[gtk.STATE_PRELIGHT] = colour label.set_style(style) item.show() submenu = item.get_submenu() if submenu: style = submenu.style.copy() style.bg_pixmap[gtk.STATE_NORMAL] = self.__pixmap submenu.set_style(style) self.set_submenu_back_pixmap(submenu) self.set_submenu_fontcolor(submenu) else: item.hide() self.__popup_menu.popup(None, None, None, button, gtk.get_current_event_time()) for item in self.__popup_menu.get_children(): if gtk.REALIZED & item.flags(): allocation = item.get_allocation() allocation.x += self.__sidebar_width allocation.width -= self.__sidebar_width item.size_allocate(allocation) self.__popup_menu.resize_children() ## FIXME: force resize popup_menu def __set_caption(self, name, caption): assert name in self.__menu_list assert isinstance(caption, basestring) item = self.ui_manager.get_widget(''.join(('/popup/', name))) if item: label = item.get_children()[0] label.set_text_with_mnemonic(caption) def __set_visible(self, name, visible): assert name in self.__menu_list assert visible in [0, 1] self.__menu_list[name]['visible'] = visible def __set_portal_menu(self, side, portal): if side >= 1: self.__set_visible('Portal', 0) else: if portal: menu = gtk.Menu() portal_list = portal.split(chr(2)) for site in portal_list: entry = site.split(chr(1)) if not entry: continue title = entry[0] if title == '-': item = gtk.SeparatorMenuItem() else: if len(entry) < 2: continue url = entry[1] ## FIXME #if len(entry) > 2: # bannar = entry[2] #else: # bannar = None item = gtk.MenuItem(title) item.connect( 'activate', lambda a, i: self.request_parent( 'NOTIFY', 'notify_site_selection', i), (title, url)) menu.add(item) item.show() menuitem = self.ui_manager.get_widget( ''.join(('/popup/', 'Portal'))) menuitem.set_submenu(menu) menu.show() self.__set_visible('Portal', 1) else: self.__set_visible('Portal', 0) def __set_recommend_menu(self, recommend): if recommend: menu = gtk.Menu() recommend_list = recommend.split(chr(2)) for site in recommend_list: entry = site.split(chr(1)) if not entry: continue title = entry[0] if title == '-': item = gtk.SeparatorMenuItem() else: if len(entry) < 2: continue url = entry[1] ## FIXME #if len(entry) > 2: # bannar = entry[2] #else: # bannar = None item = gtk.MenuItem(title) item.connect( 'activate', lambda a, i: self.request_parent( 'NOTIFY', 'notify_site_selection', i), (title, url)) menu.add(item) item.show() menuitem = self.ui_manager.get_widget( ''.join(('/popup/', 'Recommend'))) menuitem.set_submenu(menu) menu.show() self.__set_visible('Recommend', 1) else: self.__set_visible('Recommend', 0) def create_ghost_menuitem(self, name, icon, key, handler): if icon is not None: pixbuf = ninix.pix.create_icon_pixbuf(icon) if pixbuf is None: item = gtk.MenuItem(name) else: image = gtk.Image() image.set_from_pixbuf(pixbuf) image.show() item = gtk.ImageMenuItem(name) item.set_image(image) item.set_always_show_image(True) # XXX else: item = gtk.MenuItem(name) item.set_name('popup menu item') item.show() item.connect('activate', lambda a, v: handler(v), (key)) return item def __set_ghost_menu(self): for path in ['Summon', 'Change']: ghost_menu = gtk.Menu() for items in self.request_parent('GET', 'get_ghost_menus'): item = items[path] if item.get_parent(): item.reparent(ghost_menu) else: ghost_menu.append(item) menuitem = self.ui_manager.get_widget(''.join(('/popup/', path))) menuitem.set_submenu(ghost_menu) def __set_shell_menu(self): shell_menu = self.request_parent('GET', 'get_shell_menu') menuitem = self.ui_manager.get_widget(''.join(('/popup/', 'Shell'))) if menuitem.get_submenu(): menuitem.remove_submenu() menuitem.set_submenu(shell_menu) def __set_balloon_menu(self): balloon_menu = self.request_parent('GET', 'get_balloon_menu') menuitem = self.ui_manager.get_widget(''.join(('/popup/', 'Balloon'))) if menuitem.get_submenu(): menuitem.remove_submenu() menuitem.set_submenu(balloon_menu) def create_meme_menu(self, menuitem): menu = gtk.Menu() for item in menuitem.values(): if item.get_parent(): item.reparent(menu) else: menu.append(item) return menu def create_meme_menuitem(self, name, value, handler): item = gtk.MenuItem(name) item.set_name('popup menu item') item.show() item.connect('activate', lambda a, v: handler(v), (value)) return item def __set_plugin_menu(self): plugin_list = self.request_parent('GET', 'get_plugin_list') plugin_menu = gtk.Menu() for i in range(len(plugin_list)): name = plugin_list[i]['name'] item = gtk.MenuItem(name) item.set_name('popup menu item') item.show() plugin_menu.append(item) item_list = plugin_list[i]['items'] if len(item_list) <= 1: label, value = item_list[0] item.connect( 'activate', lambda a, v: self.request_parent( 'NOTIFY', 'select_plugin', v), (value)) ##if working: ## item.set_sensitive(False) else: submenu = gtk.Menu() submenu.set_name('popup menu') item.set_submenu(submenu) for label, value in item_list: item = gtk.MenuItem(label) item.set_name('popup menu item') item.connect( 'activate', lambda a, v: self.request_parent( 'NOTIFY', 'select_plugin', v), (value)) item.show() ##if working: ## item.set_sensitive(False) submenu.append(item) menuitem = self.ui_manager.get_widget(''.join(('/popup/', 'Plugin'))) menuitem.set_submenu(plugin_menu) def __set_nekodorif_menu(self): nekodorif_list = self.request_parent('GET', 'get_nekodorif_list') nekodorif_menu = gtk.Menu() for i in range(len(nekodorif_list)): name = nekodorif_list[i]['name'] item = gtk.MenuItem(name) item.set_name('popup menu item') item.show() nekodorif_menu.append(item) item.connect( 'activate', lambda a, n: self.request_parent( 'NOTIFY', 'select_nekodorif', n), (nekodorif_list[i]['dir'])) ##if working: ## item.set_sensitive(False) menuitem = self.ui_manager.get_widget( ''.join(('/popup/', 'Nekodorif'))) menuitem.set_submenu(nekodorif_menu) def __set_kinoko_menu(self): kinoko_list = self.request_parent('GET', 'get_kinoko_list') kinoko_menu = gtk.Menu() for i in range(len(kinoko_list)): name = kinoko_list[i]['title'] item = gtk.MenuItem(name) item.set_name('popup menu item') item.show() kinoko_menu.append(item) item.connect( 'activate', lambda a, k: self.request_parent( 'NOTIFY', 'select_kinoko', k), (kinoko_list[i])) ##if working: ## item.set_sensitive(False) menuitem = self.ui_manager.get_widget(''.join(('/popup/', 'Kinoko'))) menuitem.set_submenu(kinoko_menu) def get_stick(self): item = self.ui_manager.get_widget(''.join(('/popup/', 'Stick'))) return 1 if item and item.get_active() else 0 def test(): pass if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/metamagic.py000066400000000000000000000050701172114553600176270ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # metamagic.py - unknown unknowns # Copyright (C) 2011, 2012 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. # import abc class Meme(object): __metaclass__ = abc.ABCMeta def __init__(self, key): self.__key = key self.__baseinfo = None self.__menuitem = None @abc.abstractmethod def create_menuitem(self, data): pass @abc.abstractmethod def delete_by_myself(self): pass @property def key(self): # read only return self.__key @property def baseinfo(self): return self.__baseinfo @baseinfo.setter def baseinfo(self, data): ## FIXME self.__baseinfo = data menuitem = self.create_menuitem(data) if menuitem is None: self.delete_by_myself() return self.__menuitem = menuitem @property def menuitem(self): # read only return self.__menuitem class Holon(object): __metaclass__ = abc.ABCMeta def __init__(self, key): self.__key = key self.__baseinfo = None self.__menuitem = None self.__instance = None @abc.abstractmethod def create_menuitem(self, data): pass @abc.abstractmethod def delete_by_myself(self): pass @abc.abstractmethod def create_instance(self, data): pass @property def key(self): # read only return self.__key @property def baseinfo(self): # forbidden return None @baseinfo.setter def baseinfo(self, data): self.__baseinfo = data if self.__instance is None: self.__instance = self.create_instance(data) if self.__instance is None: self.delete_by_myself() return else: self.__instance.new(*data) # reset menuitem = self.create_menuitem(data) if menuitem is None: self.delete_by_myself() return self.__menuitem = menuitem @property def menuitem(self): # read only return self.__menuitem @property def instance(self): # read only return self.__instance ninix-aya-4.3.9/lib/ninix/nekodorif.py000066400000000000000000000663651172114553600176760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2004-2012 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の確率で自爆 # - 一定時間間隔で勝手に物を落とす # - ターゲット指定落し、ランダム落し、全員落し # - 出現即ヒットの場合への対応 # - 複数ゴーストでの当たり判定. # - 透明ウィンドウ import os import random import logging import gtk import glib import cairo import ninix.pix import ninix.home class Menu(object): def __init__(self, accelgroup): self.request_parent = lambda *a: None # dummy ui_info = ''' ''' self.__menu_list = { 'settings': [('Settings', None, _('Settings...(_O)'), None, '', lambda *a: self.request_parent('NOTIFY', 'edit_preferences')), '/ui/popup/Settings'], 'katochan': [('Katochan', None, _('Katochan(_K)'), None), '/ui/popup/Katochan'], 'exit': [('Exit', None,_('Exit(_Q)'), None, '', lambda *a: self.request_parent('NOTIFY', 'close')), '/ui/popup/Exit'], } self.__katochan_list = None actions = gtk.ActionGroup('Actions') entry = [value[0] for value in self.__menu_list.values()] actions.add_actions(tuple(entry)) ui_manager = gtk.UIManager() ui_manager.insert_action_group(actions, 0) ui_manager.add_ui_from_string(ui_info) self.__popup_menu = ui_manager.get_widget('/ui/popup') for key in self.__menu_list: path = self.__menu_list[key][-1] self.__menu_list[key][1] = ui_manager.get_widget(path) def set_responsible(self, request_method): self.request_parent = request_method def popup(self, button): katochan_list = self.request_parent('GET', 'get_katochan_list') self.__set_katochan_menu(katochan_list) self.__popup_menu.popup( None, None, None, button, gtk.get_current_event_time()) def __set_katochan_menu(self, list): ## FIXME key = 'katochan' if list: menu = gtk.Menu() for katochan in list: item = gtk.MenuItem(katochan['name']) item.connect( 'activate', lambda a, k: self.request_parent('NOTIFY', 'select_katochan', k), (katochan)) menu.add(item) item.show() self.__menu_list[key][1].set_submenu(menu) menu.show() self.__menu_list[key][1].show() else: self.__menu_list[key][1].hide() class Nekoninni(object): def __init__(self): self.mode = 1 # 0: SEND SSTP1.1, 1: SHIORI/2.2 self.__running = 0 self.skin = None self.katochan = None def observer_update(self, event, args): if event in ['set position', 'set surface']: if self.skin is not None: self.skin.set_position() if self.katochan is not None and self.katochan.loaded: self.katochan.set_position() elif event == 'set scale': scale = self.target.get_surface_scale() if self.skin is not None: self.skin.set_scale(scale) if self.katochan is not None: self.katochan.set_scale(scale) elif event == 'finalize': self.finalize() else: ##logging.debug('OBSERVER(nekodorif): ignore - {0}'.format(event)) pass def load(self, dir, katochan, target): if not katochan: return 0 self.dir = dir self.target = target self.target.attach_observer(self) self.accelgroup = gtk.AccelGroup() scale = self.target.get_surface_scale() self.skin = Skin(self.dir, self.accelgroup, scale) self.skin.set_responsible(self.handle_request) if self.skin is None: return 0 self.katochan_list = katochan self.katochan = None self.launch_katochan(self.katochan_list[0]) self.__running = 1 glib.timeout_add(50, self.do_idle_tasks) # 50[ms] return 1 def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { 'get_katochan_list': lambda *a: self.katochan_list, 'get_mode': lambda *a: self.mode, } handler = handlers.get(event, getattr(self, event, lambda *a: None)) ## FIXME result = handler(*arglist, **argdict) if event_type == 'GET': return result def do_idle_tasks(self): if not self.__running: return False self.skin.update() if self.katochan is not None: self.katochan.update() #self.process_script() return True def send_event(self, event): if event not in ['Emerge', # 可視領域内に出現 'Hit', # ヒット 'Drop', # 再落下開始 'Vanish', # ヒットした落下物が可視領域内から消滅 'Dodge' # よけられてヒットしなかった落下物が可視領域内から消滅 ]: return args = (self.katochan.get_name(), self.katochan.get_ghost_name(), self.katochan.get_category(), self.katochan.get_kinoko_flag(), self.katochan.get_target()) self.target.notify_event('OnNekodorifObject{0}'.format(event), *args) def has_katochan(self): return int(self.katochan is not None) def select_katochan(self, args): self.launch_katochan(args) def drop_katochan(self): self.katochan.drop() def delete_katochan(self): self.katochan.destroy() self.katochan = None self.skin.reset() def launch_katochan(self, katochan): if self.katochan: self.katochan.destroy() self.katochan = Katochan(self.target) self.katochan.set_responsible(self.handle_request) self.katochan.load(katochan) def edit_preferences(self): pass def finalize(self): self.__running = 0 self.target.detach_observer(self) if self.katochan is not None: self.katochan.destroy() if self.skin is not None: self.skin.destroy() ##if self.balloon is not None: ## self.balloon.destroy() def close(self): self.finalize() class Skin(object): def __init__(self, dir, accelgroup, scale): self.dir = dir self.accelgroup = accelgroup self.request_parent = lambda *a: None # dummy self.dragged = False self.x_root = None self.y_root = None self.__scale = scale self.__menu = Menu(self.accelgroup) self.__menu.set_responsible(self.handle_request) path = os.path.join(self.dir, 'omni.txt') self.omni = int(os.path.isfile(path) and os.path.getsize(path) == 0) self.window = ninix.pix.TransparentWindow() name, top_dir = ninix.home.read_profile_txt(dir) # XXX self.window.set_title(name) self.window.set_decorated(False) self.window.set_resizable(False) self.window.set_focus_on_map(False) self.window.connect('delete_event', self.delete) self.window.connect('key_press_event', self.key_press) self.window.realize() self.window.window.set_back_pixmap(None, False) self.window.add_accel_group(self.accelgroup) self.darea = gtk.DrawingArea() self.darea.set_events(gtk.gdk.EXPOSURE_MASK| gtk.gdk.BUTTON_PRESS_MASK| gtk.gdk.BUTTON_RELEASE_MASK| gtk.gdk.POINTER_MOTION_MASK| gtk.gdk.POINTER_MOTION_HINT_MASK| gtk.gdk.LEAVE_NOTIFY_MASK) self.darea.connect('expose_event', self.redraw) self.darea.connect('button_press_event', self.button_press) self.darea.connect('button_release_event', self.button_release) self.darea.connect('motion_notify_event', self.motion_notify) self.darea.connect('leave_notify_event', self.leave_notify) self.id = [0, None] self.darea.show() ## FIXME self.window.add(self.darea) self.darea.realize() self.set_surface() self.set_position(reset=1) self.window.show() def set_responsible(self, request_method): self.request_parent = request_method def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { } handler = handlers.get(event, getattr(self, event, None)) if handler is None: result = self.request_parent( event_type, event, *arglist, **argdict) else: result = handler(*arglist, **argdict) if event_type == 'GET': return result def set_scale(self, scale): self.__scale = scale self.set_surface() self.set_position() def redraw(self, darea, event): cr = darea.window.cairo_create() cr.save() cr.set_operator(cairo.OPERATOR_CLEAR) cr.paint() cr.restore() cr.set_source_pixbuf(self.current_surface_pixbuf, 0, 0) ##cr.paint_with_alpha(self.__alpha_channel) cr.paint() del cr def delete(self, widget, event): self.request_parent('NOTIFY', 'finalize') def key_press(self, window, event): if event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK): if event.keyval == gtk.keysyms.F12: logging.info('reset skin position') self.set_position(reset=1) return True def destroy(self): ## FIXME self.window.destroy() def button_press(self, widget, event): self.x_root = event.x_root self.y_root = event.y_root if event.button == 1: if event.type == gtk.gdk.BUTTON_PRESS: pass elif event.type == gtk.gdk._2BUTTON_PRESS: # double click if self.request_parent('GET', 'has_katochan'): self.start() ## FIXME self.request_parent('NOTIFY', 'drop_katochan') elif event.button == 3: if event.type == gtk.gdk.BUTTON_PRESS: self.__menu.popup(event.button) return True def set_surface(self): if self.id[1] is not None: path = os.path.join( self.dir, 'surface{0:d}{1:d}.png'.format(self.id[0], self.id[1])) if not os.path.exists(path): self.id[1] = None self.set_surface() return else: path = os.path.join(self.dir, 'surface{0:d}.png'.format(self.id[0])) try: surface_pixbuf = ninix.pix.create_pixbuf_from_file(path) w = max(8, surface_pixbuf.get_width() * self.__scale / 100) h = max(8, surface_pixbuf.get_height() * self.__scale / 100) surface_pixbuf = surface_pixbuf.scale_simple( w, h, gtk.gdk.INTERP_BILINEAR) except: self.request_parent('NOTIFY', 'finalize') return self.w, self.h = w, h mask_pixmap = gtk.gdk.Pixmap(None, w, h, 1) surface_pixbuf.render_threshold_alpha( mask_pixmap, 0, 0, 0, 0, w, h, 1) self.darea.set_size_request(self.w, self.h) self.window.mask = mask_pixmap self.current_surface_pixbuf = surface_pixbuf self.darea.queue_draw() def set_position(self, reset=0): left, top, scrn_w, scrn_h = ninix.pix.get_workarea() if reset: self.x = left self.y = top + scrn_h - self.h else: if not self.omni: self.y = top + scrn_h - self.h self.window.resize_move(self.x, self.y) def move(self, x_delta, y_delta): self.x = self.x + x_delta if self.omni: self.y = self.y + y_delta self.set_position() def update(self): if self.id[1] is not None: self.id[1] += 1 else: if random.choice(range(100)): ## XXX return self.id[1] = 0 self.set_surface() def start(self): ## FIXME self.id[0] = 1 self.set_surface() def reset(self): ## FIXME self.id[0] = 0 self.set_surface() def button_release(self, widget, event): if self.dragged: self.dragged = False self.set_position() self.x_root = None self.y_root = None return True def motion_notify(self, widget, event): if event.is_hint: x, y, state = self.darea.window.get_pointer() else: x, y, state = event.x, event.y, event.state if state & gtk.gdk.BUTTON1_MASK: if self.x_root is not None and \ self.y_root is not None: self.dragged = True x_delta = int(event.x_root - self.x_root) y_delta = int(event.y_root - self.y_root) self.move(x_delta, y_delta) self.x_root = event.x_root self.y_root = event.y_root return True def leave_notify(self, widget, event): ## FIXME pass class Balloon(object): def __init__(self): pass def destroy(self): ## FIXME pass class Katochan(object): CATEGORY_LIST = ['pain', # 痛い 'stab', # 刺さる 'surprise', # びっくり 'hate', # 嫌い、気持ち悪い 'huge', # 巨大 'love', # 好き、うれしい 'elegant', # 風流、優雅 'pretty', # かわいい 'food', # 食品 'reference', # 見る/読むもの 'other' # 上記カテゴリに当てはまらないもの ] def __init__(self, target): self.side = 0 self.target = target self.request_parent = lambda *a: None # dummy self.settings = {} self.settings['state'] = 'before' self.settings['fall.type'] = 'gravity' self.settings['fall.speed'] = 1 self.settings['slide.type'] = 'none' self.settings['slide.magnitude'] = 0 self.settings['slide.sinwave.degspeed'] = 30 self.settings['wave'] = None self.settings['wave.loop'] = 0 self.__scale = 100 self.loaded = False def set_responsible(self, request_method): self.request_parent = request_method def get_name(self): return self.data['name'] def get_category(self): return self.data['category'] def get_kinoko_flag(self): ## FIXME return 0 # 0/1 = きのこに当たっていない(ない場合を含む)/当たった def get_target(self): ## FIXME if self.side == 0 : return self.target.get_selfname() else: return self.target.get_keroname() def get_ghost_name(self): if 'for' in self.data: # 落下物が主に対象としているゴーストの名前 return self.data['for'] else: return '' def destroy(self): self.window.destroy() def delete(self, widget, event): self.destroy() def redraw(self, darea, event): cr = darea.window.cairo_create() cr.save() cr.set_operator(cairo.OPERATOR_CLEAR) cr.paint() cr.restore() cr.set_source_pixbuf(self.current_surface_pixbuf, 0, 0) ##cr.paint_with_alpha(self.__alpha_channel) cr.paint() del cr def set_movement(self, timing): key = ''.join((timing, 'fall.type')) if key in self.data and \ self.data[key] in ['gravity', 'evenspeed', 'none']: self.settings['fall.type'] = self.data[key] else: self.settings['fall.type'] = 'gravity' self.settings['fall.speed'] = self.data.get( ''.join((timing, 'fall.speed')), 1) if self.settings['fall.speed'] < 1: self.settings['fall.speed'] = 1 if self.settings['fall.speed'] > 100: self.settings['fall.speed'] = 100 key = ''.join((timing, 'slide.type')) if key in self.data and \ self.data[key] in ['none', 'sinwave', 'leaf']: self.settings['slide.type'] = self.data[key] else: self.settings['slide.type'] = 'none' self.settings['slide.magnitude'] = self.data.get( ''.join((timing, 'slide.magnitude')), 0) self.settings['slide.sinwave.degspeed'] = self.data.get( ''.join((timing, 'slide.sinwave.degspeed')), 30) self.settings['wave'] = self.data.get(''.join((timing, 'wave')), None) self.settings['wave.loop'] = int( self.data.get(''.join((timing, 'wave.loop'))) == 'on') def set_scale(self, scale): self.__scale = scale self.set_surface() self.set_position() def set_position(self): if self.settings['state'] != 'before': return target_x, target_y = self.target.get_surface_position(self.side) target_w, target_h = self.target.get_surface_size(self.side) left, top, scrn_w, scrn_h = ninix.pix.get_workarea() self.x = target_x + target_w / 2 - self.w / 2 + \ self.offset_x * self.__scale / 100 self.y = top + self.offset_y * self.__scale / 100 self.window.resize_move(self.x, self.y) def set_surface(self): path = os.path.join(self.data['dir'], 'surface{0:d}.png'.format(self.id)) try: surface_pixbuf = ninix.pix.create_pixbuf_from_file(path) w = max(8, surface_pixbuf.get_width() * self.__scale / 100) h = max(8, surface_pixbuf.get_height() * self.__scale / 100) surface_pixbuf = surface_pixbuf.scale_simple( w, h, gtk.gdk.INTERP_BILINEAR) except: self.request_parent('NOTIFY', 'finalize') return self.w, self.h = w, h mask_pixmap = gtk.gdk.Pixmap(None, w, h, 1) surface_pixbuf.render_threshold_alpha( mask_pixmap, 0, 0, 0, 0, w, h, 1) self.darea.set_size_request(self.w, self.h) #self.darea.queue_draw_area(0, 0, self.w, self.h) self.window.mask = mask_pixmap self.current_surface_pixbuf = surface_pixbuf self.darea.queue_draw() def load(self, data): self.data = data self.__scale = self.target.get_surface_scale() self.set_state('before') if 'category' in self.data: category = self.data['category'].split(',') if category: if category[0] not in self.CATEGORY_LIST: logging.warning('WARNING: unknown major category - {0}'.format(category[0])) ##self.data['category'] = self.CATEGORY_LIST[-1] else: self.data['category'] = self.CATEGORY_LIST[-1] else: self.data['category'] = self.CATEGORY_LIST[-1] if 'target' in self.data: if self.data['target'] == 'sakura': self.side = 0 elif self.data['target'] == 'kero': self.side = 1 else: self.side = 0 # XXX else: self.side = 0 # XXX if self.request_parent('GET', 'get_mode') == 1: self.request_parent('NOTIFY', 'send_event', 'Emerge') else: if 'before.script' in self.data: pass ## FIXME else: pass ## FIXME self.set_movement('before') if 'before.appear.direction' in self.data: pass ## FIXME else: pass ## FIXME offset_x = self.data.get('before.appear.ofset.x', 0) if offset_x < -32768: offset_x = -32768 if offset_x > 32767: offset_x = 32767 offset_y = self.data.get('before.appear.ofset.y', 0) if offset_y < -32768: offset_y = -32768 if offset_y > 32767: offset_y = 32767 self.offset_x = offset_x self.offset_y = offset_y self.window = ninix.pix.TransparentWindow() self.window.set_title(self.data['name']) self.window.set_decorated(False) self.window.set_resizable(False) self.window.set_skip_taskbar_hint(True) # XXX self.window.set_focus_on_map(False) self.window.connect('delete_event', self.delete) self.darea = gtk.DrawingArea() self.darea.set_events(gtk.gdk.EXPOSURE_MASK) self.darea.connect('expose_event', self.redraw) self.darea.show() ## FIXME self.window.add(self.darea) self.darea.realize() self.window.realize() self.window.window.set_back_pixmap(None, False) self.window.show() self.id = 0 self.set_surface() self.set_position() self.loaded = True def drop(self): ## FIXME self.set_state('fall') def set_state(self, state): self.settings['state'] = state self.time = 0 self.hit = 0 self.hit_stop = 0 def update_surface(self): ## FIXME pass def update_position(self): ## FIXME if self.settings['slide.type'] == 'leaf': pass else: if self.settings['fall.type'] == 'gravity': self.y += int(self.settings['fall.speed'] * \ (self.time / 20.0)**2) elif self.settings['fall.type'] == 'evenspeed': self.y += self.settings['fall.speed'] else: pass if self.settings['slide.type'] == 'sinwave': pass ## FIXME else: pass self.window.resize_move(self.x, self.y) def check_collision(self): ## FIXME: check self position for side in [0, 1]: target_x, target_y = self.target.get_surface_position(side) target_w, target_h = self.target.get_surface_size(side) center_x = self.x + self.w / 2 center_y = self.y + self.h / 2 if target_x < center_x < target_x + target_w and \ target_y < center_y < target_y + target_h: self.side = side return 1 else: return 0 def check_mikire(self): left, top, scrn_w, scrn_h = ninix.pix.get_workarea() if self.x + self.w - self.w / 3 > left + scrn_w or \ self.x + self.w / 3 < left or \ self.y + self.h - self.h / 3 > top + scrn_h or \ self.y + self.h / 3 < top: return 1 else: return 0 def update(self): ## FIXME if self.settings['state'] == 'fall': self.update_surface() self.update_position() if self.check_collision(): self.set_state('hit') self.hit = 1 if self.request_parent('GET', 'get_mode') == 1: self.id = 1 self.set_surface() self.request_parent('NOTIFY', 'send_event', 'Hit') else: pass ## FIXME if self.check_mikire(): self.set_state('dodge') elif self.settings['state'] == 'hit': if self.hit_stop >= self.data.get('hit.waittime', 0): self.set_state('after') self.set_movement('after') if self.request_parent('GET', 'get_mode') == 1: self.id = 2 self.set_surface() self.request_parent('NOTIFY', 'send_event', 'Drop') else: pass ## FIXME else: self.hit_stop += 1 self.update_surface() elif self.settings['state'] == 'after': self.update_surface() self.update_position() if self.check_mikire(): self.set_state('end') elif self.settings['state'] == 'end': if self.request_parent('GET', 'get_mode') == 1: self.request_parent('NOTIFY', 'send_event', 'Vanish') else: pass ## FIXME self.request_parent('NOTIFY', 'delete_katochan') return False elif self.settings['state'] == 'dodge': if self.request_parent('GET', 'get_mode') == 1: self.request_parent('NOTIFY', 'send_event', 'Dodge') else: pass ## FIXME self.request_parent('NOTIFY', 'delete_katochan') return False else: pass ## check collision and mikire self.time += 1 return True ninix-aya-4.3.9/lib/ninix/ngm.py000066400000000000000000000667331172114553600164760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001-2004 by MATSUMURA Namihiko # Copyright (C) 2004-2012 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. # import codecs import os import re import time import urllib import webbrowser import logging from xml.dom import pulldom import gtk import gobject import cairo import ninix.home import ninix.pix import ninix.install # 注意: # - このURLを本ゴーストマネージャクローン以外の用途で使う場合は, # 「できるだけ事前に」AQRS氏に連絡をお願いします. # (「何かゴーストマネージャ」のページ: http://www.aqrs.jp/ngm/) # - アクセスには帯域を多く使用しますので, # 必ず日時指定の差分アクセスをし余分な負荷をかけないようお願いします. # (差分アクセスの方法については本プログラムの実装が参考になると思います.) MASTERLIST_URL = 'http://www.aqrs.jp/cgi-bin/ghostdb/request2.cgi' # 10000以上のIDを持つデータは仮登録 ID_LIMIT = 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(object): #public methods def __init__(self, datadir): self.data = {} self.url = {} self.cgi = {} self.datadir = datadir if not os.path.exists(self.datadir): os.makedirs(self.datadir) self.last_update = '1970-01-01 00:00:00' self.load_MasterList() # data handling functions def get(self, entry, key): return entry.get(key) # XXX # updates etc def network_update(self, updatehook): last_update = self.last_update self.last_update = time.strftime('%Y-%m-%d %H:%M:%S') if self.cgi: priority = sorted(self.cgi.keys()) url = self.cgi[priority[-1]][-1] else: url = MASTERLIST_URL try: f = urllib.urlopen(url, urllib.urlencode( {'time': '"{0}"'.format(last_update), 'charset': 'UTF-8'})) except: return ## FIXME for _ in self.import_from_fileobj(f): updatehook() self.save_MasterList() f.close() # private methods def load_MasterList(self): try: with open(os.path.join(self.datadir, 'MasterList.xml'), 'r') as f: for _ in self.import_from_fileobj(f): pass except IOError: return def save_MasterList(self): with open(os.path.join(self.datadir, 'MasterList.xml'), 'w') as f: self.export_to_fileobj(f) def get_encoding(self, line): m = re.compile('<\?xml version="1.0" encoding="(.+)" \?>').search(line) return m.group(1) def create_entry(self, node): entry = {} for key, text in node: assert key in ELEMENTS entry[key] = text return entry def import_from_fileobj(self, fileobj): line0 = fileobj.readline() encoding = self.get_encoding(line0) try: codecs.lookup(encoding) except: raise SystemExit, 'Unsupported encoding {0}'.format(repr(encoding)) nest = 0 new_entry = {} set_id = None node = [] re_list = re.compile('') re_setid = re.compile('') re_set = re.compile('') re_priority = re.compile('(.+)') re_misc = re.compile('<(.+)>(.+)') for line in fileobj: yield assert nest >= 0 line = unicode(line, encoding, 'ignore').encode('utf-8') if not line: continue m = re_list.search(line) if m: nest += 1 continue m = re_setid.search(line) if m: nest += 1 set_id = int(m.group(1)) continue m = re_set.search(line) if m: nest -= 1 new_entry[set_id] = self.create_entry(node) node = [] continue m = re_priority.search(line) if m: g = m.groups() priority = int(g[0]) url = g[1] if priority in self.cgi: self.cgi[priority].append(url) else: self.cgi[priority] = [url] continue m = re_misc.search(line) if m: g = m.groups() if set_id is not None: key = g[0] text = g[1] text = text.replace(''', '\'') text = text.replace('"', '"') text = text.replace('>', '>') text = text.replace('<', '<') text = text.replace('&', '&') node.append([key, text]) else: key = g[0] text = g[1] assert key in ['LastUpdate', 'NGMVersion', 'SakuraPreviewBaseUrl', 'KeroPreviewBaseUrl', 'ArcMD5BaseUrl', 'NGMUpdateBaseUrl'] if key == 'LastUpdate': self.last_update = text elif key == 'NGMVersion': version = float(text) if version < 0.51: return else: self.version = version elif key in ['SakuraPreviewBaseUrl', 'KeroPreviewBaseUrl', 'ArcMD5BaseUrl', 'NGMUpdateBaseUrl']: self.url[key] = text self.data.update(new_entry) def dump_entry(self, entry, fileobj): for key in ELEMENTS: if key in entry and entry[key] is not None: text = entry[key] text = text.replace('&', '&') text = text.replace('<', '<') text = text.replace('>', '>') text = text.replace('"', '"') text = text.replace('\'', ''') text = text.encode('UTF-8') else: text = '' # fileobj.write(' <{0}/>\n'.format(key)) fileobj.write(' <{0}>{1}\n'.format(key, text)) def export_to_fileobj(self, fileobj): fileobj.write('\n') fileobj.write('\n') fileobj.write('{0}\n'.format(self.last_update)) for key in ['SakuraPreviewBaseUrl', 'KeroPreviewBaseUrl', 'ArcMD5BaseUrl', 'NGMUpdateBaseUrl']: if key in self.url: fileobj.write('<{0}>{1}\n'.format(key, self.url[key])) else: fileobj.write('<{0}>\n'.format(key)) # fileobj.write('<{0}/>\n'.format(key)) fileobj.write('{0}\n'.format(self.version)) key_list = sorted(self.cgi.keys()) key_list.reverse() for priority in key_list: for url in self.cgi[priority]: fileobj.write( '{1}\n'.format(priority, url)) ids = sorted(self.data.keys()) for set_id in ids: fileobj.write('\n'.format(set_id)) entry = self.data[set_id] self.dump_entry(entry, fileobj) fileobj.write('\n') fileobj.write('\n') class Catalog(Catalog_xml): TYPE = ['Sakura', 'Kero'] def image_filename(self, set_id, side): p = '{0}_{1:d}.png'.format(self.TYPE[side], set_id) d = os.path.join(self.datadir, p) return d if os.path.exists(d) else None def retrieve_image(self, set_id, side, updatehook): p = '{0}_{1:d}.png'.format(self.TYPE[side], set_id) d = os.path.join(self.datadir, p) if not os.path.exists(d): url = self.url['SakuraPreviewBaseUrl'] if side == 0 else \ self.url['KeroPreviewBaseUrl'] try: urllib.urlretrieve(''.join((url, p)), d, updatehook) except: return ## FIXME class SearchDialog(object): def __init__(self): self.request_parent = lambda *a: None # dummy self.dialog = gtk.Dialog() self.dialog.connect('delete_event', self.cancel) self.dialog.set_modal(True) self.dialog.set_position(gtk.WIN_POS_CENTER) label = gtk.Label(_('Search for')) self.dialog.vbox.pack_start(label) self.pattern_entry = gtk.Entry() self.pattern_entry.set_size_request(300, -1) self.dialog.vbox.pack_start(self.pattern_entry) self.dialog.vbox.show_all() self.ok_button = gtk.Button(_('OK')) self.ok_button.connect('clicked', self.ok) self.dialog.action_area.pack_start(self.ok_button) button = gtk.Button(_('Cancel')) button.connect('clicked', self.cancel) self.dialog.action_area.pack_start(button) self.dialog.action_area.show_all() def set_responsible(self, request_method): self.request_parent = request_method def set_pattern(self, text): self.pattern_entry.set_text(text) def get_pattern(self): return self.pattern_entry.get_text() def hide(self): self.dialog.hide() def show(self, default=None): if default: self.set_pattern(default) self.dialog.show() def ok(self, widget, event=None): word = unicode(self.get_pattern(), 'utf-8') self.request_parent('NOTIFY', 'search', word) self.hide() return True def cancel(self, widget, event=None): self.hide() return True class UI(object): def __init__(self): self.request_parent = lambda *a: None # dummy self.ui_info = ''' ''' self.entries = ( ( 'FileMenu', None, _('_File') ), ( 'ViewMenu', None, _('_View') ), ( 'ArchiveMenu', None, _('_Archive') ), ( 'HelpMenu', None, _('_Help') ), ( 'Search', None, # name, stock id _('Search(_F)'), 'F', # label, accelerator 'Search', # tooltip lambda *a: self.open_search_dialog() ), ( 'Search Forward', None, _('Search Forward(_S)'), 'F3', None, lambda *a: self.search_forward() ), ( 'Settings', None, _('Settings(_O)'), None, None, lambda *a: self.request_parent( 'NOTIFY', 'open_preference_dialog') ), ( 'DB Network Update', None, _('DB Network Update(_N)'), None, None, lambda *a: self.network_update() ), ( 'Close', None, _('Close(_X)'), None, None, lambda *a: self.close() ), ( 'Mask', None, _('Mask(_M)'), None, None, lambda *a: self.request_parent( 'NOTIFY', 'open_mask_dialog') ), ( 'Reset to Default', None, _('Reset to Default(_Y)'), None, None, lambda *a: self.request_parent( 'NOTIFY', 'reset_to_default') ), ( 'Show All', None, _('Show All(_Z)'), None, None, lambda *a: self.request_parent( 'NOTIFY', 'show_all') ), ) self.opened = 0 self.textview = [None, None] self.darea = [None, None] self.pixbuf = [None, None] self.info = None self.button = {} self.url = {} self.search_word = '' self.search_dialog = SearchDialog() self.search_dialog.set_responsible(self.handle_request) self.create_dialog() def set_responsible(self, request_method): self.request_parent = request_method def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { } handler = handlers.get(event, getattr(self, event, None)) if handler is None: result = self.request_parent( event_type, event, *arglist, **argdict) else: result = handler(*arglist, **argdict) if event_type == 'GET': return result def create_dialog(self): self.window = gtk.Window() self.window.set_title(_('Ghost Manager')) self.window.set_resizable(False) self.window.connect('delete_event', lambda *a: self.close()) self.window.set_position(gtk.WIN_POS_CENTER) self.window.set_gravity(gtk.gdk.GRAVITY_CENTER) actions = gtk.ActionGroup('Actions') actions.add_actions(self.entries) ui = gtk.UIManager() ui.insert_action_group(actions, 0) self.window.add_accel_group(ui.get_accel_group()) try: mergeid = ui.add_ui_from_string(self.ui_info) except gobject.GError as msg: logging.error('building menus failed: {0}'.format(msg)) vbox = gtk.VBox(False, 0) self.window.add(vbox) vbox.show() vbox.pack_start(ui.get_widget('/MenuBar'), False, False, 0) separator = gtk.HSeparator() vbox.pack_start(separator, False, True, 0) separator.show() hbox = gtk.HBox(False, 0) vbox.pack_start(hbox, False, True, 10) hbox.show() self.surface_area_sakura = self.create_surface_area(0) hbox.pack_start(self.surface_area_sakura, False, True, 10) self.surface_area_kero = self.create_surface_area(1) hbox.pack_start(self.surface_area_kero, False, True, 10) self.info_area = self.create_info_area() hbox.pack_start(self.info_area, False, True, 10) box = gtk.HButtonBox() box.set_layout(gtk.BUTTONBOX_SPREAD) vbox.pack_start(box, False, True, 4) box.show() button = gtk.Button(_('Previous')) button.connect('clicked', lambda b, w=self: w.show_previous()) box.add(button) button.show() self.button['previous'] = button button = gtk.Button(_('Next')) button.connect('clicked', lambda b, w=self: w.show_next()) box.add(button) button.show() self.button['next'] = button self.statusbar = gtk.Statusbar() vbox.pack_start(self.statusbar, False, True, 0) self.statusbar.show() def network_update(self): self.window.set_sensitive(False) def updatehook(*args): while gtk.events_pending(): gtk.main_iteration() self.request_parent('NOTIFY', 'network_update', updatehook) self.update() self.window.set_sensitive(True) def open_search_dialog(self): self.search_dialog.show(default=self.search_word) def search(self, word): if word: self.search_word = word if self.request_parent('GET', 'search', word): self.update() else: pass ## FIXME def search_forward(self): if self.search_word: if self.request_parent('GET', 'search_forward', self.search_word): self.update() else: pass ## FIXME def show_next(self): self.request_parent('NOTIFY', 'next') self.update() def show_previous(self): self.request_parent('NOTIFY', 'previous') self.update() def create_surface_area(self, side): assert side in [0, 1] vbox = gtk.VBox(False, 0) vbox.show() textview = gtk.TextView() textview.set_editable(False) textview.set_size_request(128, 16) vbox.pack_start(textview, False, True, 0) textview.show() self.textview[side] = textview darea = gtk.DrawingArea() vbox.pack_start(darea, False, True, 0) darea.show() darea.set_events(gtk.gdk.EXPOSURE_MASK) darea.connect('expose_event', self.redraw, side) self.darea[side] = darea return vbox def redraw(self, darea, event, side): cr = darea.window.cairo_create() cr.save() cr.set_operator(cairo.OPERATOR_CLEAR) cr.paint() cr.restore() if self.pixbuf[side] is not None: cr.set_source_pixbuf(self.pixbuf[side], 0, 0) cr.paint() del cr def update_surface_area(self): for side in [0, 1]: target = 'SakuraName' if side == 0 else 'KeroName' name = self.request_parent('GET', 'get', target) textbuffer = self.textview[side].get_buffer() textbuffer.set_text(name) filename = self.request_parent('GET', 'get_image_filename', side) darea = self.darea[side] darea.realize() if filename is not None: try: pixbuf = ninix.pix.create_pixbuf_from_file(filename) except: pixbuf = None else: w = pixbuf.get_width() h = pixbuf.get_height() darea.set_size_request(w, h) mask = gtk.gdk.Pixmap(None, w, h, 1) pixbuf.render_threshold_alpha( mask, 0, 0, 0, 0, w, h, 1) darea.window.shape_combine_mask(mask, 0, 0) else: pixbuf = None self.pixbuf[side] = pixbuf darea.queue_draw() def create_info_area(self): vbox = gtk.VBox(False, 0) vbox.show() hbox = gtk.HBox(False, 0) box = gtk.HButtonBox() box.set_layout(gtk.BUTTONBOX_SPREAD) box.show() button = gtk.Button(_('Install')) button.connect( 'clicked', lambda b, w=self: w.request_parent('NOTIFY', 'install_current')) box.add(button) button.show() self.button['install'] = button button = gtk.Button(_('Update')) button.connect( 'clicked', lambda b, w=self: w.request_parent('NOTIFY', 'update_current')) box.add(button) button.show() self.button['update'] = button hbox.pack_start(box, True, True, 10) vbox2 = gtk.VBox(False, 0) hbox.pack_start(vbox2, False, True, 0) vbox2.show() button = gtk.Button('') # with GtkLabel button.set_relief(gtk.RELIEF_NONE) self.url['HP'] = [None, button.get_child()] vbox2.pack_start(button, False, True, 0) button.connect( 'clicked', lambda b: webbrowser.open(self.url['HP'][0])) button.show() button = gtk.Button('') button.set_relief(gtk.RELIEF_NONE) button.set_use_underline(True) self.url['Public'] = [None, button.get_child()] vbox2.pack_start(button, False, True, 0) button.connect( 'clicked', lambda b: webbrowser.open(self.url['Public'][0])) button.show() vbox.pack_start(hbox, False, True, 0) hbox.show() textview = gtk.TextView() textview.set_editable(False) textview.set_size_request(256, 144) vbox.pack_start(textview, False, True, 0) textview.show() self.info = textview return vbox def update_info_area(self): info_list = [(_('Author:'), 'Author'), (_('ArchiveTime:'), 'ArchiveTime'), (_('ArchiveSize:'), 'ArchiveSize'), (_('NetworkUpdateTime:'), 'NetworkUpdateTime'), (_('Version:'), 'Version'), (_('AIName:'), 'AIName')] text = '' text = ''.join((text, self.request_parent('GET', 'get', 'Name'), '\n')) for item in info_list: text = ''.join((text, item[0], self.request_parent('GET', 'get', item[1]), '\n')) text = ''.join((text, self.request_parent('GET', 'get', 'SakuraName'), _('SurfaceList:'), self.request_parent('GET', 'get', 'SakuraSurfaceList'), '\n')) text = ''.join((text, self.request_parent('GET', 'get', 'KeroName'), _('SurfaceList:'), self.request_parent('GET', 'get', 'KeroSurfaceList'), '\n')) textbuffer = self.info.get_buffer() textbuffer.set_text(text) url = self.request_parent('GET', 'get', 'HPUrl') text = self.request_parent('GET', 'get', 'HPTitle') self.url['HP'][0] = url label = self.url['HP'][1] label.set_markup('{0}'.format(text)) url = self.request_parent('GET', 'get', 'PublicUrl') text = ''.join((self.request_parent('GET', 'get', 'Name'), _(' Web Page'))) self.url['Public'][0] = url label = self.url['Public'][1] label.set_markup('{0}'.format(text)) target_dir = os.path.join( self.request_parent('GET', 'get_home_dir'), 'ghost', self.request_parent('GET', 'get', 'InstallDir')) self.button['install'].set_sensitive( bool(not os.path.isdir(target_dir) and self.request_parent('GET', 'get', 'ArchiveUrl') != 'No data')) self.button['update'].set_sensitive( bool(os.path.isdir(target_dir) and self.request_parent('GET', 'get', 'GhostType') == 'ゴースト' and self.request_parent('GET', 'get', 'UpdateUrl') != 'No data')) def update(self): self.update_surface_area() self.update_info_area() self.button['next'].set_sensitive( bool(self.request_parent('GET', 'exist_next'))) self.button['previous'].set_sensitive( bool(self.request_parent('GET', 'exist_previous'))) def show(self): if self.opened: return self.update() self.window.show() self.opened = 1 def close(self): self.window.hide() self.opened = 0 return True class NGM(object): def __init__(self): self.request_parent = lambda *a: None # dummy self.current = 0 self.opened = 0 self.home_dir = ninix.home.get_ninix_home() self.catalog = Catalog(os.path.join(self.home_dir, 'ngm/data')) self.installer = ninix.install.Installer() self.ui = UI() self.ui.set_responsible(self.handle_request) def set_responsible(self, request_method): self.request_parent = request_method def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { 'get_home_dir': lambda *a: self.home_dir, } handler = handlers.get(event, getattr(self, event, None)) if handler is None: result = self.request_parent( event_type, event, *arglist, **argdict) else: result = handler(*arglist, **argdict) if event_type == 'GET': return result def get(self, element): if self.current in self.catalog.data: entry = self.catalog.data[self.current] text = self.catalog.get(entry, element) if text is not None: return text return 'No data' def get_image_filename(self, side): return self.catalog.image_filename(self.current, side) def search(self, word, set_id=0): while set_id < ID_LIMIT: if set_id in self.catalog.data: entry = self.catalog.data[set_id] for element in ['Name', 'SakuraName', 'KeroName', 'Author', 'HPTitle']: text = self.catalog.get(entry, element) if not text: continue text = unicode(text, 'utf-8') if word in text: self.current = set_id return True set_id += 1 return False def search_forward(self, word): return self.search(word, set_id=self.current + 1) def open_preference_dialog(self): pass def network_update(self, updatehook): self.catalog.network_update(updatehook) for set_id in self.catalog.data: for side in [0, 1]: self.catalog.retrieve_image(set_id, side, updatehook) def open_mask_dialog(self): pass def reset_to_default(self): pass def show_all(self): pass def next(self): next = self.current + 1 if next < ID_LIMIT and next in self.catalog.data: self.current = next def exist_next(self): next = self.current + 1 return bool(next < ID_LIMIT and next in self.catalog.data) def previous(self): previous = self.current - 1 assert previous < ID_LIMIT if previous in self.catalog.data: self.current = previous def exist_previous(self): previous = self.current - 1 assert previous < ID_LIMIT return bool(previous in self.catalog.data) def show_dialog(self): self.ui.show() def install_current(self): try: filetype, target_dir = self.installer.install( self.get('ArchiveUrl'), ninix.home.get_ninix_home()) except: target_dir = None assert filetype == 'ghost' if target_dir is not None: self.request_parent('NOTIFY', 'add_sakura', target_dir) def update_current(self): ## FIXME self.request_parent('NOTIFY', 'update_sakura', self.get('Name'), 'NGM') if __name__ == '__main__': pass ninix-aya-4.3.9/lib/ninix/pix.py000066400000000000000000000244611172114553600165050ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2003-2012 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. # import logging import os import hashlib import logging import numpy import gtk class TransparentWindow(gtk.Window): __gsignals__ = {'screen-changed': 'override', } def __init__(self, type=gtk.WINDOW_TOPLEVEL): gtk.Window.__init__(self, type) self.set_app_paintable(True) self.screen_changed() self.truncated = False self.mask_offset = (0, 0) fixed = gtk.Fixed() fixed.show() gtk.Window.add(self, fixed) # XXX self.__fixed = fixed self.__mask = None self.__child = None self.__position = (0, 0) self.connect_after('size_allocate', self.size_allocate) @property def mask(self): return self.__mask @mask.setter def mask(self, mask): x, y = self.mask_offset self.__mask = mask if self.is_composited(): self.input_shape_combine_mask(mask, x, y) else: self.shape_combine_mask(mask, x, y) @property def fixed(self): # read only return self.__fixed def add(self, widget): self.fixed.put(widget, 0, 0) self.__child = widget def remove(self, widget): self.fixed.remove(widget) self.__child = None def destroy(self): gtk.Window.remove(self, self.fixed) self.fixed.destroy() gtk.Window.destroy(self) def screen_changed(self, old_screen=None): screen = self.get_screen() if self.is_composited(): colormap = screen.get_rgba_colormap() self.supports_alpha = True else: logging.debug('screen does NOT support alpha.\n') colormap = screen.get_rgb_colormap() self.supports_alpha = False self.set_colormap(colormap) def size_allocate(self, widget, event): new_x, new_y = self.__position gtk.Window.move(self, new_x, new_y) def reset_truncate(self): self.fixed.move(self.__child, 0, 0) self.set_size_request(-1, -1) if self.is_composited(): self.input_shape_combine_mask(self.mask, 0, 0) else: self.shape_combine_mask(self.mask, 0, 0) self.mask_offset = (0, 0) self.truncated = False def resize_move(self, x, y, xoffset=0, yoffset=0): ### XXX: does not work propery for gtk.WINDOW_POPUP ##if self.type == gtk.WINDOW_POPUP: ## gtk.Window.move(self, x, y) ## return w, h = self.__child.get_size_request() left, top, scrn_w, scrn_h = get_workarea() new_x = min(max(x + xoffset, left - w + 1), left + scrn_w - 1) new_y = min(max(y + yoffset, top - h + 1), top + scrn_h - 1) new_w = w new_h = h offset_x = 0 offset_y = 0 truncate = False if new_x < left: offset_x = new_x - left new_x = left new_w += offset_x truncate = True if new_x + w > left + scrn_w: new_w -= new_x + w - (left + scrn_w) truncate = True if new_y < top: offset_y = new_y - top new_y = top new_h += offset_y truncate = True if new_y + h > top + scrn_h: new_h -= new_y + h - (top + scrn_h) truncate = True if not truncate: if self.truncated: self.reset_truncate() else: gtk.Window.move(self, new_x, new_y) # XXX else: self.fixed.move(self.__child, offset_x, offset_y) self.set_size_request(new_w, new_h) if self.is_composited(): self.input_shape_combine_mask(self.mask, offset_x, offset_y) else: self.shape_combine_mask(self.mask, offset_x, offset_y) self.truncated = True self.mask_offset = (offset_x, offset_y) self.__position = (new_x, new_y) self.__child.queue_draw() def get_png_size(path): if not path or not os.path.exists(path): return 0, 0 head, tail = os.path.split(path) basename, ext = os.path.splitext(tail) ext = ext.lower() if ext == '.dgp': buf = get_DGP_IHDR(path) elif ext == '.ddp': buf = get_DDP_IHDR(path) else: buf = get_png_IHDR(path) assert buf[0:8] == '\x89PNG\r\n\x1a\n' # png format assert buf[12:16] == 'IHDR' # name of the first chunk in a PNG datastream w = buf[16:20] h = buf[20:24] width = (ord(w[0]) << 24) + (ord(w[1]) << 16) + (ord(w[2]) << 8) + ord(w[3]) height = (ord(h[0]) << 24) + (ord(h[1]) << 16) + (ord(h[2]) << 8) + ord(h[3]) return width, height def get_DGP_IHDR(path): head, tail = os.path.split(path) filename = tail m_half = hashlib.md5(filename[:len(filename) / 2]).hexdigest() m_full = hashlib.md5(filename).hexdigest() tmp = ''.join((m_full, filename)) key = '' j = 0 for i in range(len(tmp)): value = ord(tmp[i]) ^ ord(m_half[j]) if not value: break key = ''.join((key, chr(value))) j += 1 if j >= len(m_half): j = 0 key_length = len(key) if key_length == 0: # not encrypted logging.warning(''.join((filename, ' generates a null key.'))) return get_png_IHDR(path) key = ''.join((key[1:], key[0])) key_pos = 0 buf = '' with open(path, 'rb') as f: for i in range(24): c = f.read(1) buff = ''.join((buf, chr(ord(c) ^ ord(key[key_pos])))) key_pos += 1 if key_pos >= key_length: key_pos = 0 return buf def get_DDP_IHDR(path): size = os.path.getsize(path) key = size << 2 buf = '' with open(path, 'rb') as f: for i in range(24): c = f.read(1) key = (key * 0x08088405 + 1) & 0xffffffff buf = ''.join((buf, chr((ord(c) ^ key >> 24) & 0xff))) return buf def get_png_IHDR(path): with open(path, 'rb') as f: buf = f.read(24) return buf def __pixbuf_new_from_file(path): if os.name == 'nt': # XXX path = unicode(path, 'mbcs').encode('utf-8') return gtk.gdk.pixbuf_new_from_file(path) def create_icon_pixbuf(path): try: pixbuf = __pixbuf_new_from_file(path) except: # compressed icons are not supported. :-( pixbuf = None else: pixbuf = pixbuf.scale_simple(16, 16, gtk.gdk.INTERP_BILINEAR) return pixbuf def create_blank_pixbuf(width, height): pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, width, height) pixbuf.fill(0xffffffffL) return pixbuf def create_pixbuf_from_DGP_file(path): head, tail = os.path.split(path) filename = tail m_half = hashlib.md5(filename[:len(filename) / 2]).hexdigest() m_full = hashlib.md5(filename).hexdigest() tmp = ''.join((m_full, filename)) key = '' j = 0 for i in range(len(tmp)): value = ord(tmp[i]) ^ ord(m_half[j]) if not value: break key = ''.join((key, chr(value))) j += 1 if j >= len(m_half): j = 0 key_length = len(key) if key_length == 0: # not encrypted logging.warning(''.join((filename, ' generates a null key.'))) pixbuf = __pixbuf_new_from_file(filename) return pixbuf key = ''.join((key[1:], key[0])) key_pos = 0 loader = gtk.gdk.PixbufLoader('png') with open(path, 'rb') as f: while 1: c = f.read(1) if c == '': break loader.write(chr(ord(c) ^ ord(key[key_pos])), 1) key_pos += 1 if key_pos >= key_length: key_pos = 0 pixbuf = loader.get_pixbuf() loader.close() return pixbuf def create_pixbuf_from_DDP_file(path): with open(path, 'rb') as f: buf = f.read() key = len(buf) << 2 loader = gtk.gdk.PixbufLoader('png') for i in range(len(buf)): key = (key * 0x08088405 + 1) & 0xffffffff loader.write(chr((ord(buf[i]) ^ key >> 24) & 0xff), 1) pixbuf = loader.get_pixbuf() loader.close() return pixbuf def create_pixbuf_from_file(path, is_pnr=True, use_pna=False): head, tail = os.path.split(path) basename, ext = os.path.splitext(tail) ext = ext.lower() if ext == '.dgp': pixbuf = create_pixbuf_from_DGP_file(path) elif ext == '.ddp': pixbuf = create_pixbuf_from_DDP_file(path) else: pixbuf = __pixbuf_new_from_file(path) if is_pnr: array = pixbuf.get_pixels_array() if not pixbuf.get_has_alpha(): r, g, b = array[0][0] pixbuf = pixbuf.add_alpha(True, chr(r), chr(g), chr(b)) else: ar = numpy.frombuffer(array, numpy.uint32) rgba = ar[0] ar[ar == rgba] = 0x00000000 if use_pna: path = os.path.join(head, ''.join((basename, '.pna'))) if os.path.exists(path): assert pixbuf.get_has_alpha() pna_pixbuf = __pixbuf_new_from_file(path) pna_array = pna_pixbuf.get_pixels_array() assert pna_pixbuf.get_bits_per_sample() / 8 == 1 pixbuf_array = pixbuf.get_pixels_array() pixbuf_array[:,:,3] = pna_array[:,:,0] return pixbuf def create_pixmap_from_file(path): pixbuf = create_pixbuf_from_file(path) pixmap, mask = pixbuf.render_pixmap_and_mask(1) return pixmap, mask def get_workarea(): scrn = gtk.gdk.screen_get_default() root = scrn.get_root_window() if not hasattr(scrn, 'supports_net_wm_hint') or \ not scrn.supports_net_wm_hint('_NET_CURRENT_DESKTOP') or \ not scrn.supports_net_wm_hint('_NET_WORKAREA'): left, top, width, height, depth = root.get_geometry() else: index = root.property_get('_NET_CURRENT_DESKTOP')[2][0] * 4 left, top, width, height = root.property_get('_NET_WORKAREA')[2][index:index+4] return left, top, width, height ninix-aya-4.3.9/lib/ninix/plugin.py000066400000000000000000000127411172114553600172010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import os import sys import socket import multiprocessing import StringIO class BasePlugin(multiprocessing.Process): def __init__(self, port, directory, args, ninix_home, caller, queue, data): multiprocessing.Process.__init__(self) self.queue = queue self.__cache = data self.sstp_port = port self.directory = directory self.args = args self.ninix_home = ninix_home self.caller = caller if os.name == 'nt' and directory is not None: sys.path.insert(0, directory) # XXX def set_variable(self, name, value): self.set_variables({name: value}) def set_variables(self, v_dict): assert isinstance(v_dict, dict) for name, value in v_dict.items(): assert isinstance(name, basestring) and ':' not in name assert isinstance(value, basestring) or value is None self.queue.put(v_dict) self.queue.join() self.__cache.update(v_dict) def get_variable(self, name): if name in self.__cache: return self.__cache[name] else: return None def open_dialog(self, message): self.queue.put('DIALOG:{0}'.format(message)) self.queue.join() data = self.queue.get(True) self.queue.task_done() assert isinstance(data, basestring) return data def _connect(self): address = ('localhost', self.sstp_port) sstp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sstp.connect(address) except socket.error: raise SystemExit, 'cannot connect to the SSTP server.' return sstp def send_sstp(self, sender, entry=[], script_odict=None): if self.sstp_port is None: return sstp = self._connect() assert script_odict if any(script_odict.keys()): version = 1.4 elif entry: version = 1.2 else: version = 1.1 message = ''.join(('SEND SSTP/{0}\r\n'.format(version), 'Charset: UTF-8\r\n', 'Sender: ', sender, '\r\n')) for value in entry: message = ''.join((message, 'Entry: ', value, '\r\n')) if script_odict is not None: for if_ghost, script in script_odict.items(): if if_ghost: message = ''.join((message, 'IfGhost: ', if_ghost, '\r\n')) message = ''.join((message, 'Script: ', script, '\r\n')) message = ''.join((message, '\r\n')) sstp.send(message.encode('utf-8')) buffer = [] while 1: buffer.append(sstp.recv(1024)) if not buffer[-1]: break sstp.close() data = ''.join(buffer) if not data: return '' file = StringIO.StringIO(data) status = file.readline() if status[:12] != 'SSTP/{0} 200'.format(version): ## FIXME return '' for line in file: line = line.strip('\r\n\r\n') if not line: continue return unicode(line, 'utf-8') return '' def notify_sstp(self, sender, event, ref=[], entry=[], script_odict=None): if self.sstp_port is None: return sstp = self._connect() message = ''.join(('NOTIFY SSTP/1.1\r\n', 'Charset: UTF-8\r\n', 'Sender: ', sender, '\r\n', 'Event: ', event, '\r\n')) assert len(ref) <= 8 for i, value in enumerate(ref): message = ''.join((message, 'Reference{0}: '.format(i), value, '\r\n')) for value in entry: message = ''.join((message, 'Entry: ', value, '\r\n')) if script_odict is not None: for if_ghost, script in script_odict.items(): if if_ghost: message = ''.join((message, 'IfGhost: ', if_ghost, '\r\n')) message = ''.join((message, 'Script: ', script, '\r\n')) message = ''.join((message, '\r\n')) sstp.send(message.encode('utf-8')) buffer = [] while 1: buffer.append(sstp.recv(1024)) if not buffer[-1]: break sstp.close() data = ''.join(buffer) if not data: return '' file = StringIO.StringIO(data) status = file.readline() if status[:12] != 'SSTP/1.1 200': ## FIXME return '' for line in file: line = line.strip('\r\n\r\n') if not line: continue return unicode(line, 'utf-8') return '' def send_script(self, sender, script, if_ghost=None): self.send_sstp(sender, script_odict={if_ghost: script}) ninix-aya-4.3.9/lib/ninix/prefs.py000066400000000000000000000420471172114553600170240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2004-2012 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. # import mimetools import os import gtk import gobject import ninix.home range_scale = list(range(100, 30, -10)) + [200, 300, 1000] range_script_speed = list(range(-1, 5)) + [6, 8] # -1: no wait # default settings DEFAULT_BALLOON_FONTS = 'Sans' def get_default_surface_scale(): return range_scale[0] def get_default_script_speed(): return range_script_speed[len(range_script_speed) // 2] class Preferences(dict): def __init__(self, filename): dict.__init__(self) self.filename = filename self.__stack = {} def __setitem__(self, key, item): if key in self and key not in self.__stack: self.__stack[key] = self[key] dict.__setitem__(self, key, item) def commit(self): self.__stack = {} def revert(self): self.update(self.__stack) self.__stack = {} def load(self): self.clear() try: with open(self.filename) as f: prefs = mimetools.Message(f) for key, value in prefs.items(): self[key] = value except IOError: return def save(self): try: os.makedirs(os.path.dirname(self.filename)) except OSError: pass with open(self.filename, 'w') as f: keys = sorted(self.keys()) for key in keys: if key in self.__stack: value = self.__stack[key] else: value = self[key] f.write('{0}: {1}\n'.format(key, value)) def get_with_type(self, name, conv, default): value = self.get(name) if value: if conv is None: return value try: return conv(value) except ValueError: pass return default class PreferenceDialog(object): PREFS_TYPE = {'sakura_name': None, # XXX: backward compat 'sakura_dir': None, 'default_balloon': None, 'ignore_default': int, 'script_speed': int, 'surface_scale': int, 'balloon_scalling': int, 'balloon_fonts': None, 'allowembryo': int, 'check_collision': int, 'use_pna': int, 'sink_after_talk': int, 'raise_before_talk': int, 'surface_alpha': float, 'balloon_alpha': float, 'animation_quality': float, 'seriko_inactive': int, } def __init__(self): self.request_parent = lambda *a: None # dummy self.window = gtk.Dialog() self.window.set_title('Preferences') self.window.connect('delete_event', self.cancel) self.notebook = gtk.Notebook() self.notebook.set_tab_pos(gtk.POS_TOP) self.window.vbox.pack_start(self.notebook) self.notebook.show() for name, constructor in [ (_('Font'), self.make_page_fonts), (_('Surface&Balloon'), self.make_page_surface_n_balloon), (_('Misc'), self.make_page_misc), (_('Debug'), self.make_page_debug), ]: self.notebook.append_page(constructor(), gtk.Label(unicode(name, 'utf-8'))) box = gtk.HButtonBox() box.set_layout(gtk.BUTTONBOX_END) self.window.action_area.pack_start(box) box.show() button = gtk.Button('OK') button.connect('clicked', self.ok) box.add(button) button.show() button = gtk.Button('Apply') button.connect('clicked', self.apply) box.add(button) button.show() button = gtk.Button('Cancel') button.connect('clicked', self.cancel) box.add(button) button.show() def set_responsible(self, request_method): self.request_parent = request_method def load(self): filename = ninix.home.get_preferences() self.__prefs = Preferences(filename) self.save = self.__prefs.save self.__prefs.load() self.reset() self.update() # XXX self.request_parent('NOTIFY', 'notify_preference_changed') def reset(self): ### FIXME ### self.fontsel.set_font_name( self.get('balloon_fonts', DEFAULT_BALLOON_FONTS)) self.set_default_balloon(self.get('default_balloon')) self.ignore_button.set_active( bool(self.get('ignore_default', 0))) scale = self.get('surface_scale', get_default_surface_scale()) self.surface_scale_combo.set_active( range_scale.index(get_default_surface_scale() if scale not in range_scale else scale)) script_speed = self.get('script_speed', get_default_script_speed()) self.script_speed_combo.set_active( range_script_speed.index(get_default_script_speed() if script_speed not in range_script_speed else script_speed)) self.balloon_scalling_button.set_active( bool(self.get('balloon_scalling'))) self.allowembryo_button.set_active( bool(self.get('allowembryo'))) self.check_collision_button.set_active( bool(self.get('check_collision', 0))) self.use_pna_button.set_active( bool(self.get('use_pna', 1))) self.sink_after_talk_button.set_active( bool(self.get('sink_after_talk'))) self.surface_alpha_adjustment.set_value(self.get('surface_alpha', 1.0)) self.balloon_alpha_adjustment.set_value(self.get('balloon_alpha', 1.0)) self.raise_before_talk_button.set_active( bool(self.get('raise_before_talk'))) self.animation_quality_adjustment.set_value( self.get('animation_quality', 1.0)) self.seriko_inactive_button.set_active( bool(self.get('seriko_inactive'))) def get(self, name, default=None): assert name in self.PREFS_TYPE return self.__prefs.get_with_type(name, self.PREFS_TYPE[name], default) def set_current_sakura(self, directory): key = 'sakura_name' # obsolete if key in self.__prefs: del self.__prefs[key] key = 'sakura_dir' if key in self.__prefs: del self.__prefs[key] self.__prefs[key] = directory def edit_preferences(self): self.show() def update(self): ## FIXME self.__prefs['allowembryo'] = str(int(self.allowembryo_button.get_active())) self.__prefs['balloon_fonts'] = self.fontsel.get_font_name() selected = self.balloon_treeview.get_selection().get_selected() if selected: model, listiter = selected directory = model.get_value(listiter, 1) self.__prefs['default_balloon'] = directory self.__prefs['ignore_default'] = str(int(self.ignore_button.get_active())) self.__prefs['surface_scale'] = str(int(range_scale[self.surface_scale_combo.get_active()])) self.__prefs['script_speed'] = str(int(range_script_speed[self.script_speed_combo.get_active()])) self.__prefs['balloon_scalling'] = str(int(self.balloon_scalling_button.get_active())) self.__prefs['check_collision'] = str(int(self.check_collision_button.get_active())) self.__prefs['use_pna'] = str(int(self.use_pna_button.get_active())) self.__prefs['sink_after_talk'] = str(int(self.sink_after_talk_button.get_active())) self.__prefs['raise_before_talk'] = str(int(self.raise_before_talk_button.get_active())) self.__prefs['surface_alpha'] = str(float(self.surface_alpha_adjustment.get_value())) self.__prefs['balloon_alpha'] = str(float(self.balloon_alpha_adjustment.get_value())) self.__prefs['animation_quality'] = str(float(self.animation_quality_adjustment.get_value())) self.__prefs['seriko_inactive'] = str(int(self.seriko_inactive_button.get_active())) def ok(self, widget): self.hide() self.update() self.__prefs.commit() self.request_parent('NOTIFY', 'notify_preference_changed') def apply(self, widget): self.update() self.request_parent('NOTIFY', 'notify_preference_changed') def cancel(self, widget, event=None): self.hide() self.__prefs.revert() self.reset() self.request_parent('NOTIFY', 'notify_preference_changed') return True def show(self): self.window.show() def hide(self): self.window.hide() def make_page_fonts(self): page = gtk.VBox(spacing=5) page.set_border_width(5) # font frame = gtk.Frame(unicode(_('Font(s) for balloons'), 'utf-8')) frame.set_size_request(480, -1) page.pack_start(frame) frame.show() self.fontsel = gtk.FontSelection() self.fontsel.show() frame.add(self.fontsel) page.show() return page def make_page_surface_n_balloon(self): page = gtk.VBox(spacing=5) page.set_border_width(5) page.show() frame = gtk.Frame(unicode(_('Surface Scaling'), 'utf-8')) frame.set_size_request(480, -1) page.pack_start(frame, False) frame.show() box = gtk.VBox(spacing=5) box.set_border_width(5) frame.add(box) box.show() hbox = gtk.HBox(spacing=5) box.pack_start(hbox, False) hbox.show() label = gtk.Label(unicode(_('Default Setting'), 'utf-8')) hbox.pack_start(label, False) label.show() self.surface_scale_combo = gtk.combo_box_new_text() for value in range_scale: self.surface_scale_combo.append_text('{0:4d}%'.format(value)) hbox.pack_start(self.surface_scale_combo, False) self.surface_scale_combo.show() button = gtk.CheckButton(unicode(_('Scale Balloon'), 'utf-8')) self.balloon_scalling_button = button box.pack_start(button, False) button.show() frame = gtk.Frame(unicode(_('Default Balloon'), 'utf-8')) frame.set_size_request(480, -1) page.pack_start(frame) frame.show() box = gtk.VBox(spacing=5) box.set_border_width(5) frame.add(box) box.show() scrolled = gtk.ScrolledWindow() scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scrolled.set_shadow_type(gtk.SHADOW_ETCHED_IN) box.pack_start(scrolled, True) scrolled.show() treeview = gtk.TreeView(None) column = gtk.TreeViewColumn( _('Balloon Name'), gtk.CellRendererText(), text=0) treeview.append_column(column) treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) self.balloon_treeview = treeview scrolled.add(treeview) treeview.show() button = gtk.CheckButton(unicode(_('Always Use This Balloon'), 'utf-8')) self.ignore_button = button box.pack_start(button, False) button.show() frame = gtk.Frame(unicode(_('Translucency'), 'utf-8')) frame.set_size_request(480, -1) page.pack_start(frame, False) frame.show() box = gtk.VBox(spacing=5) box.set_border_width(5) frame.add(box) box.show() button = gtk.CheckButton(unicode(_('Use PNA file'), 'utf-8')) self.use_pna_button = button box.pack_start(button, False) button.show() hbox = gtk.HBox(spacing=5) box.add(hbox) hbox.show() label = gtk.Label(unicode(_("Surfaces' alpha channel"), 'utf-8')) hbox.pack_start(label, False) label.show() self.surface_alpha_adjustment = gtk.Adjustment(1.0, 0.1, 1.0, 0.1, 0.5) button = gtk.SpinButton(self.surface_alpha_adjustment, 0.2, 1) hbox.pack_start(button, False) button.show() hbox = gtk.HBox(spacing=5) box.add(hbox) hbox.show() label = gtk.Label(unicode(_("Balloons' alpha channel"), 'utf-8')) hbox.pack_start(label, False) label.show() self.balloon_alpha_adjustment = gtk.Adjustment(1.0, 0.1, 1.0, 0.1, 0.5) button = gtk.SpinButton(self.balloon_alpha_adjustment, 0.2, 1) hbox.pack_start(button, False) button.show() frame = gtk.Frame(unicode(_('Animation'), 'utf-8')) frame.set_size_request(480, -1) page.pack_start(frame, False) frame.show() box = gtk.VBox(spacing=5) box.set_border_width(5) frame.add(box) box.show() hbox = gtk.HBox(spacing=5) box.add(hbox) hbox.show() label = gtk.Label(unicode(_('Quality'), 'utf-8')) hbox.pack_start(label, False) label.show() self.animation_quality_adjustment = gtk.Adjustment(1.0, 0.1, 1.0, 0.1, 0.1) button = gtk.SpinButton(self.animation_quality_adjustment, 0.2, 1) hbox.pack_start(button, False) button.show() hbox.show() button = gtk.CheckButton(unicode(_('SERIKO INACTIVE'), 'utf-8')) self.seriko_inactive_button = button box.pack_start(button, False) button.show() return page def make_page_misc(self): page = gtk.VBox(spacing=5) page.set_border_width(5) page.show() frame = gtk.Frame(unicode(_('SSTP Setting'), 'utf-8')) frame.set_size_request(480, -1) page.pack_start(frame, False) frame.show() button = gtk.CheckButton(unicode(_('Allowembryo'), 'utf-8')) self.allowembryo_button = button frame.add(button) button.show() frame = gtk.Frame(unicode(_('Script Wait'), 'utf-8')) frame.set_size_request(480, -1) page.pack_start(frame, False) frame.show() hbox = gtk.HBox(spacing=5) frame.add(hbox) hbox.show() label = gtk.Label(unicode(_('Default Setting'), 'utf-8')) hbox.pack_start(label, False) label.show() self.script_speed_combo = gtk.combo_box_new_text() for index in range(len(range_script_speed)): if index == 0: label = _('None') elif index == 1: label = ''.join(('1 (', _('Fast'), ')')) elif index == len(range_script_speed) - 1: label = ''.join((str(index), ' (', _('Slow'), ')')) else: label = str(index) self.script_speed_combo.append_text(label) hbox.pack_start(self.script_speed_combo, False) self.script_speed_combo.show() frame = gtk.Frame(unicode(_('Raise & Lower'), 'utf-8')) frame.set_size_request(480, -1) page.pack_start(frame, False) frame.show() box = gtk.VBox(spacing=5) box.set_border_width(5) frame.add(box) box.show() button = gtk.CheckButton(unicode(_('Sink after Talk'), 'utf-8')) self.sink_after_talk_button = button box.pack_start(button, False) button.show() button = gtk.CheckButton(unicode(_('Raise before Talk'), 'utf-8')) self.raise_before_talk_button = button box.pack_start(button, False) button.show() return page def make_page_debug(self): page = gtk.VBox(spacing=5) page.set_border_width(5) page.show() frame = gtk.Frame(unicode(_('Surface Debugging'), 'utf-8')) frame.set_size_request(480, -1) page.pack_start(frame, False) frame.show() button = gtk.CheckButton(unicode(_('Display Collision Area'), 'utf-8')) self.check_collision_button = button frame.add(button) button.show() return page def set_default_balloon(self, directory): model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) for name, directory in self.request_parent('GET', 'get_balloon_list'): listiter = model.append() model.set_value(listiter, 0, name) model.set_value(listiter, 1, directory) self.balloon_treeview.set_model(model) listiter = model.get_iter_first() while listiter is not None: value = model.get_value(listiter, 1) if value == directory or directory is None: self.balloon_treeview.get_selection().select_iter(listiter) break listiter = model.iter_next(listiter) else: listiter = model.get_iter_first() assert listiter is not None self.balloon_treeview.get_selection().select_iter(listiter) ninix-aya-4.3.9/lib/ninix/sakura.py000066400000000000000000002213021172114553600171640ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import codecs import logging import os import random import re import select import socket import sys import time import webbrowser import urllib from collections import OrderedDict import gtk ## FIXME import glib try: import gconf ## FIXME except: gconf = None # For some reasons gst parses sys.argv on import. This is a workaround. # (See https://bugzilla.gnome.org/show_bug.cgi?id=549879 .) argv = sys.argv sys.argv = [] try: ##import pygst ##pygst.require('0.10') # This breaks sys.path. :-( import gst except: gst = None sys.argv = argv import ninix.surface import ninix.balloon import ninix.dll import ninix.makoto import ninix.pix import ninix.script import ninix.version import ninix.update from ninix.home import get_ninix_home, get_normalized_path from ninix.metamagic import Meme class ShellMeme(Meme): def __init__(self, key): Meme.__init__(self, key) self.request_parent = lambda *a: None # dummy def set_responsible(self, request_method): self.request_parent = request_method def create_menuitem(self, data): shell_name = data[0] return self.request_parent( 'GET', 'create_shell_menuitem', shell_name, self.key) def delete_by_myself(self): self.request_parent('NOTIFY', 'delete_shell', self.key) class Sakura(object): 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 # script origins FROM_SSTP_CLIENT = 1 FROM_GHOST = 2 # HTML entity definitions try: from htmlentitydefs import name2codepoint except: name2codepoint = None def __init__(self): self.request_parent = lambda *a: None # dummy self.sstp_handle = None self.sstp_entry_db = None self.sstp_request_handler = None # error = 'loose'(default) or 'strict' self.script_parser = ninix.script.Parser(error='loose') self.char = 2 # 'sakura' and 'kero' self.script_queue = [] self.script_mode = self.BROWSE_MODE self.script_post_proc = [] self.event_queue = [] self.__current_script = '' self.__balloon_life = 0 self.__surface_life = 0 self.__boot = [0, 0] self.surface_mouse_motion = None ## FIXME self.time_critical_session = 0 self.lock_repaint = 0 self.passivemode = 0 self.__running = 0 self.anchor = None self.clock = (0, 0) self.synchronized_session = [] ## self.old_otherghostname = None ## FIXME # create vanish dialog self.__vanish_dialog = VanishDialog() self.__vanish_dialog.set_responsible(self.handle_request) self.cantalk = 1 self.__sender = 'ninix-aya' self.__charset = 'Shift_JIS' saori_lib = ninix.dll.Library('saori', sakura=self) self.__dll = ninix.dll.Library('shiori', saori_lib=saori_lib) self.__temp_mode = 0 self.__observers = {} self.balloon = ninix.balloon.Balloon() self.balloon.set_responsible(self.handle_request) self.surface = ninix.surface.Surface() self.surface.set_responsible(self.handle_request) self.keep_silence(False) self.updateman = ninix.update.NetworkUpdate() self.updateman.set_responsible(self.handle_request) if gst is not None: self.audio_player = gst.element_factory_make('playbin', 'player') fakesink = gst.element_factory_make('fakesink', 'fakesink') self.audio_player.set_property('video-sink', fakesink) else: self.audio_player = None def set_responsible(self, request_method): self.request_parent = request_method def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { } handler = handlers.get(event, getattr(self, event, None)) if handler is None: result = self.request_parent( event_type, event, *arglist, **argdict) else: result = handler(*arglist, **argdict) if event_type == 'GET': return result def attach_observer(self, observer): if observer not in self.__observers: self.__observers[observer] = 1 def notify_observer(self, event, args=None): args = args or () for observer in self.__observers.keys(): observer.observer_update(event, args) def detach_observer(self, observer): if observer in self.__observers: del self.__observers[observer] def delete_shell(self, key): assert key in self.shells del self.shells[key] def get_shell_menu(self): current_key = self.get_current_shell() for key in self.shells: menuitem = self.shells[key].menuitem menuitem.set_sensitive(key != current_key) # not working return self.shell_menu def new(self, desc, shiori_dir, use_makoto, surface_set, prefix, shiori_dll, shiori_name): ## FIXME self.shiori = None self.desc = desc self.shiori_dir = shiori_dir self.use_makoto = use_makoto self.shells = OrderedDict() shell_menuitems = OrderedDict() for key, value in surface_set.items(): meme = ShellMeme(key) meme.set_responsible(self.handle_request) self.shells[key] = meme meme.baseinfo = value shell_menuitems[key] = meme.menuitem self.shell_menu = self.request_parent( 'GET', 'create_shell_menu', shell_menuitems) self.prefix = prefix self.shiori_dll = shiori_dll self.shiori_name = shiori_name name = (shiori_dll, shiori_name) self.shiori = self.__dll.request(name) char = 2 while self.desc.get('char{0:d}.seriko.defaultsurface'.format(char)) is not None: char += 1 if char > 2: self.char = char # XXX if self.desc.get('name') == u'BTH小っちゃいってことは便利だねっ': self.set_SSP_mode(1) else: self.set_SSP_mode(0) def set_SSP_mode(self, flag): # XXX self.__sender = 'SSP' if flag else 'ninix-aya' def save_history(self): path = os.path.join(self.get_prefix(), 'HISTORY') try: with open(path, 'w') as f: f.write('time, {0}\n'.format(self.ghost_time)) f.write('vanished_count, {0}\n'.format(self.vanished_count)) except IOError as e: code, message = e.args logging.error('cannot write {0}'.format(path)) def save_settings(self): path = os.path.join(self.get_prefix(), 'SETTINGS') try: with open(path, 'w') as f: if self.balloon_directory is not None: f.write('balloon_directory, {0}\n'.format( self.balloon_directory)) if self.shell_directory is not None: f.write('shell_directory, {0}\n'.format( self.shell_directory)) except IOError as e: code, message = e.args logging.error('cannot write {0}'.format(path)) def load_history(self): path = os.path.join(self.get_prefix(), 'HISTORY') if os.path.exists(path): ghost_time = 0 ghost_vanished_count = 0 try: with open(path, 'r') as f: for line in f: if ',' not in line: continue key, value = line.split(',', 1) key = key.strip() if key == 'time': try: ghost_time = int(value.strip()) except: pass elif key == 'vanished_count': try: ghost_vanished_count = int(value.strip()) except: pass except IOError as e: code, message = e.args logging.error('cannot read {0}'.format(path)) self.ghost_time = ghost_time self.vanished_count = ghost_vanished_count else: self.ghost_time = 0 self.vanished_count = 0 def load_settings(self): path = os.path.join(self.get_prefix(), 'SETTINGS') if os.path.exists(path): balloon_directory = None shell_directory = None try: with open(path, 'r') as f: for line in f: if ',' not in line: continue key, value = line.split(',', 1) if key.strip() == 'balloon_directory': balloon_directory = value.strip() if key.strip() == 'shell_directory': shell_directory = value.strip() except IOError as e: code, message = e.args logging.error('cannot read {0}'.format(path)) self.balloon_directory = balloon_directory self.shell_directory = shell_directory else: self.balloon_directory = None self.shell_directory = None def load_shiori(self): if self.shiori and self.shiori.load(self.shiori_dir): if getattr(self.shiori, 'show_description', None): self.shiori.show_description() else: logging.error('{0} cannot load SHIORI({1})'.format( self.get_selfname().encode('utf-8', 'ignore'), self.shiori_name)) self.__charset = 'Shift_JIS' # default self.get_event_response('basewareversion', ninix.version.VERSION, 'ninix-aya', ninix.version.NUMBER, event_type='NOTIFY') def finalize(self): if not self.__temp_mode: self.shiori.unload() self.stop() def enter_temp_mode(self): if not self.__temp_mode: self.__temp_mode = 2 def leave_temp_mode(self): self.__temp_mode = 0 def set_surface(self, desc, alias, surface, name, surface_dir, tooltips): self.surface.new(desc, alias, surface, name, surface_dir, tooltips) for side in range(2, self.char): default = self.desc.get('char{0:d}.seriko.defaultsurface'.format(side)) self.surface.add_window(side, default) icon = self.desc.get('icon', None) if icon is not None: icon_path = os.path.join(self.shiori_dir, icon) if not os.path.exists(icon_path): icon_path = None else: icon_path = None self.surface.set_icon(icon_path) def set_balloon(self, desc, balloon): self.balloon.new(desc, balloon) for side in range(2, self.char): self.balloon.add_window(side) for side in range(self.char): balloon_win = self.balloon.get_window(side) surface_win = self.surface.get_window(side) balloon_win.set_transient_for(surface_win) def enqueue_script(self, script, sender, handle, host, show_sstp_marker, use_translator, db=None, request_handler=None): if not self.script_queue and \ not self.time_critical_session and not self.passivemode: if self.sstp_request_handler: self.sstp_request_handler.send_sstp_break() self.sstp_request_handler = None self.reset_script(1) self.script_queue.append((script, sender, handle, host, show_sstp_marker, use_translator, db, request_handler)) reset_event = ['OnGhostChanging', 'OnShellChanging', 'OnVanishSelected'] def check_event_queue(self): return bool(self.event_queue) def enqueue_event(self, event, *arglist, **argdict): ## FIXME for key in argdict: assert key in ['proc'] # trap typo, etc. if event in self.reset_event: self.reset_script(1) self.event_queue.append((event, arglist, argdict)) EVENT_SCRIPTS = { 'OnUpdateBegin': \ ''.join((r'\t\h\s[0]', unicode(_('Network Update has begun.'), 'utf-8'), r'\e')), 'OnUpdateComplete': \ ''.join((r'\t\h\s[5]', unicode(_('Network Update completed successfully.'), 'utf-8'), r'\e')), 'OnUpdateFailure': \ ''.join((r'\t\h\s[4]', unicode(_('Network Update failed.'), 'utf-8'), r'\e')), } def handle_event(self): ## FIXME while self.event_queue: event, arglist, argdict = self.event_queue.pop(0) proc = argdict.get('proc', None) argdict = {'default': self.EVENT_SCRIPTS.get(event)} if self.notify_event(event, *arglist, **argdict): if proc is not None: self.script_post_proc.append(proc) return 1 elif proc is not None: proc() return 1 return 0 def is_running(self): return self.__running def is_paused(self): return self.script_mode == self.PAUSE_MODE def is_talking(self): return int(self.processed_script or self.processed_text) def busy(self, check_updateman=True): return bool(self.time_critical_session or \ self.balloon.user_interaction or \ self.event_queue or \ self.passivemode or \ self.sstp_request_handler is not None or \ (check_updateman and self.updateman.is_active())) def get_silent_time(self): return self.silent_time def keep_silence(self, quiet): if quiet: self.silent_time = time.time() else: self.silent_time = 0 self.reset_idle_time() def get_idle_time(self): now = time.time() idle = now - self.idle_start return idle def reset_idle_time(self): self.idle_start = time.time() def notify_preference_changed(self): ## FIXME self.balloon.reset_fonts() ## FIXME self.surface.reset_surface() self.notify_observer('set scale') ## FIXME self.balloon.reset_balloon() def get_surface_position(self, side): result = self.surface.get_position(side) return result if result is not None else (0, 0) def set_balloon_position(self, side, base_x, base_y): self.balloon.set_position(side, base_x, base_y) def set_balloon_direction(self, side, direction): self.balloon.set_direction(side, direction) def get_balloon_size(self, side): result = self.balloon.get_balloon_size(side) return result if result is not None else (0, 0) def get_balloon_windowposition(self, side): return self.balloon.get_balloon_windowposition(side) def get_balloon_position(self, side): result = self.balloon.get_position(side) return result if result is not None else (0, 0) def balloon_is_shown(self, side): return int(self.balloon and self.balloon.is_shown(side)) def surface_is_shown(self, side): return int(self.surface and self.surface.is_shown(side)) def is_URL(self, s): return s.startswith('http://') or \ s.startswith('ftp://') or \ s.startswith('file:/') def is_anchor(self, link_id): return int(len(link_id) == 2 and link_id[0] == 'anchor') def vanish(self): if self.busy(): gtk.gdk.beep() ## FIXME return self.notify_event('OnVanishSelecting') self.__vanish_dialog.show() def vanish_by_myself(self): self.vanished_count += 1 self.ghost_time = 0 self.request_parent( 'NOTIFY', 'stop_sakura', lambda a: self.request_parent('NOTIFY', 'vanish_sakura', a), (self)) def get_ifghost(self): return ''.join((self.get_selfname(), ',', self.get_keroname())) def ifghost(self, ifghost): names = self.get_ifghost() name = self.get_selfname() return bool(ifghost in [name, names]) def get_name(self, default=unicode(_('Sakura&Unyuu'), 'utf-8')): return self.desc.get('name', default) def get_username(self): return self.getstring('username') or \ self.surface.get_username() or \ self.desc.get('user.defaultname', unicode(_('User'), 'utf-8')) def get_selfname(self, default=unicode(_('Sakura'), 'utf-8')): return self.surface.get_selfname() or \ self.desc.get('sakura.name', default) def get_selfname2(self): return self.surface.get_selfname2() or \ self.desc.get('sakura.name2', unicode(_('Sakura'), 'utf-8')) def get_keroname(self): return self.surface.get_keroname() or \ self.desc.get('kero.name', unicode(_('Unyuu'), 'utf-8')) def get_friendname(self): return self.surface.get_friendname() or \ self.desc.get('sakura.friend.name', unicode(_('Tomoyo'), 'utf-8')) def getaistringrandom(self): # obsolete result = self.get_event_response('OnAITalk') return self.translate(result) def getdms(self): result = self.get_event_response('dms') return self.translate(result) def getword(self, word_type): result = self.get_event_response(word_type) return self.translate(result) def getstring(self, name): return self.get_event_response(name) def translate(self, s): if s is not None: if self.use_makoto: s = ninix.makoto.execute(s) else: r = self.get_event_response('OnTranslate', s, translate=0) if r: s = r return s def get_value(self, response): # FIXME: check return code result = {} to = None for line in response.splitlines(): line = line.strip() if not line: continue if ':' not in line: continue key, value = line.split(':', 1) key = key.strip() if key == 'Charset': charset = value.strip() if charset != self.__charset: try: codecs.lookup(charset) except: logging.warning( 'Unsupported charset {0}'.format(repr(charset))) else: self.__charset = charset result[key] = value for key, value in result.items(): result[key] = unicode(value, self.__charset, 'ignore').strip() if 'Reference0' in result: to = result['Reference0'] if 'Value' in result: return result['Value'], to else: return None, to def get_event_response_with_communication(self, event, *arglist, **argdict): if self.__temp_mode == 1: return '' for key in argdict: assert key in ['event_type', 'translate'] # trap typo, etc. ref = arglist event_type = argdict.get('event_type', 'GET') translate = argdict.get('translate', 1) header = ''.join(('{0} SHIORI/3.0\r\n'.format(event_type), 'Sender: {0}\r\n'.format(self.__sender), 'ID: {0}\r\n'.format(event), 'SecurityLevel: local\r\n', 'Charset: {0}\r\n'.format(self.__charset))) for i in range(len(ref)): value = ref[i] if value is not None: value = value if isinstance(value, basestring) \ else str(value) header = ''.join((header, 'Reference', str(i), ': ', value, '\r\n')) header = ''.join((header, '\r\n')) header = header.encode(self.__charset, 'ignore') response = self.shiori.request(header) if event_type != 'NOTIFY' and self.cantalk: result, to = self.get_value(response) if translate: result = self.translate(result) else: result, to = '', None if result is None: result = '' if to and result: def proc(): self.request_parent( 'NOTIFY', 'send_message', to, self.get_selfname(), result) communication = proc else: communication = None return result, communication def get_event_response(self, event, *arglist, **argdict): result, communication = self.get_event_response_with_communication(event, *arglist, **argdict) return result ### CALLBACK ### def notify_start(self, init, vanished, ghost_changed, name, path): if self.__temp_mode: default = None else: default = ninix.version.VERSION_INFO if init: if self.ghost_time == 0: if not self.notify_event('OnFirstBoot', self.vanished_count, None, None, None, None, None, None, self.surface.name): self.notify_event('OnBoot', self.surface.name, default=default) else: self.notify_event('OnBoot', self.surface.name, default=default) left, top, scrn_w, scrn_h = ninix.pix.get_workarea() self.notify_event('OnDisplayChange', gtk.gdk.visual_get_best_depth(), scrn_w, scrn_h, event_type='NOTIFY') elif vanished: if self.ghost_time == 0: if self.notify_event('OnFirstBoot', self.vanished_count, None, None, None, None, None, None, self.surface.name): return elif self.notify_event('OnVanished', name): return self.notify_event('OnBoot', self.surface.name, default=default) elif ghost_changed: if self.ghost_time == 0: if self.notify_event('OnFirstBoot', self.vanished_count, None, None, None, None, None, None, self.surface.name): return elif self.notify_event('OnGhostChanged', name): return self.notify_event('OnBoot', self.surface.name, default=default) else: pass ## FIXME def notify_vanish_selected(self): def proc(self=self): self.vanished_count += 1 self.ghost_time = 0 glib.idle_add( lambda a: self.request_parent('NOTIFY', 'vanish_sakura', a), (self)) self.enqueue_event('OnVanishSelected', proc=proc) self.vanished = 1 ## FIXME def notify_vanish_canceled(self): self.notify_event('OnVanishCancel') def notify_iconified(self): self.cantalk = 0 self.request_parent('NOTIFY', 'select_current_sakura') if not self.passivemode: self.reset_script(1) self.stand_by(1) self.notify_event('OnWindowStateMinimize') self.notify_observer('iconified') def notify_deiconified(self): if not self.cantalk: self.cantalk = 1 self.request_parent('NOTIFY', 'select_current_sakura') if not self.passivemode: self.notify_event('OnWindowStateRestore') self.notify_observer('deiconified') def notify_link_selection(self, link_id, text, number): if self.script_origin == self.FROM_SSTP_CLIENT and \ self.sstp_request_handler is not None: self.sstp_request_handler.send_answer(text) self.sstp_request_handler = None if self.is_anchor(link_id): self.notify_event('OnAnchorSelect', link_id[1]) elif self.is_URL(link_id): webbrowser.open(link_id) self.reset_script(1) self.stand_by(0) elif self.sstp_entry_db: # leave the previous sstp message as it is self.start_script(self.sstp_entry_db.get(link_id, r'\e')) self.sstp_entry_db = None elif not self.notify_event('OnChoiceSelect', link_id, text, number): self.reset_script(1) self.stand_by(0) def notify_site_selection(self, args): title, url = args if self.is_URL(url): webbrowser.open(url) self.enqueue_event('OnRecommandedSiteChoice', title, url) def notify_surface_click(self, button, click, side, x, y): if button == 1 and click == 1: self.raise_all() if self.vanished: if side == 0 and button == 1: if self.sstp_request_handler: self.sstp_request_handler.send_sstp_break() self.sstp_request_handler = None self.reset_script(1) self.notify_event('OnVanishButtonHold', default=r'\e') self.vanished = 0 return if self.updateman.is_active(): if button == 1 and click == 2: self.updateman.interrupt() return if self.time_critical_session: return elif button in [1, 3] and click == 1: if self.passivemode and \ self.processed_script is not None: return part = self.surface.get_touched_region(side, x, y) self.notify_event('OnMouseClick', x, y, 0, side, part, button) elif self.passivemode: return elif button == 1 and click == 2: if self.sstp_request_handler: self.sstp_request_handler.send_sstp_break() self.sstp_request_handler = None part = self.surface.get_touched_region(side, x, y) self.notify_event('OnMouseDoubleClick', x, y, '', side, part) def notify_balloon_click(self, button, click, side): if self.script_mode == self.PAUSE_MODE: self.script_mode = self.BROWSE_MODE self.balloon.clear_text_all() self.balloon.hide_all() self.script_side = 0 elif button == 1 and click == 1: self.raise_all() if self.vanished: return if self.updateman.is_active(): if button == 1 and click == 2: self.updateman.interrupt() return if self.time_critical_session: self.time_critical_session = 0 return elif self.passivemode: return elif button == 1 and click == 2: if self.sstp_request_handler: self.sstp_request_handler.send_sstp_break() self.sstp_request_handler = None self.reset_script(1) self.stand_by(0) elif button == 3 and click == 1: if self.sstp_request_handler: self.sstp_request_handler.send_sstp_break() self.sstp_request_handler = None self.reset_script(1) self.stand_by(0) def notify_surface_mouse_motion(self, side, x, y, part): if self.surface_mouse_motion is not None: return if part: self.surface_mouse_motion = (side, x, y, part) else: self.surface_mouse_motion = None def notify_user_teach(self, word): if word is not None: script = self.translate(self.get_event_response('OnTeach', word)) if script: self.start_script(script) self.balloon.hide_sstp_message() month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] boot_event = ['OnBoot', 'OnFirstBoot', 'OnGhostChanged', 'OnShellChanged', 'OnUpdateComplete'] reset_event = ['OnVanishSelecting', 'OnVanishCancel'] ## FIXME def notify_event(self, event, *arglist, **argdict): #if self.time_critical_session: # return 0 if event in self.reset_event: self.reset_script(1) for key in argdict: assert key in ['event_type', 'default'] # trap typo, etc. event_type = argdict.get('event_type', 'GET') default = argdict.get('default', None) argdict = {'event_type': event_type} ## FIXME script, communication = self.get_event_response_with_communication(event, *arglist, **argdict) or (default, None) if script or (not script and event != 'OnSecondChange'): t = time.localtime(time.time()) m = self.month_names[t[1] - 1] logging.debug('\n[{0:02d}/{1}/{2:d}:{3:02d}:{4:02d}:{5:02d} {6:+05d}]'.format( t[2], m, t[0], t[3], t[4], t[5], - time.timezone / 36)) logging.debug('Event: {0}'.format(event)) for n in range(len(arglist)): value = arglist[n] if value is not None: value = value if isinstance(value, basestring) \ else str(value) logging.debug( 'Reference{0:d}: {1}'.format( n, value.encode('utf-8', 'ignore'))) if not script: # an empty script is ignored if event in self.boot_event: self.surface_bootup() if event == 'OnMouseClick' and arglist[5] == 3: self.request_parent( 'NOTIFY', 'open_popup_menu', self, arglist[5], arglist[3]) return 0 logging.debug('=> "{0}"'.format(script.encode('utf-8', 'ignore'))) if self.__temp_mode == 2: self.request_parent('NOTIFY', 'reset_sstp_flag') self.leave_temp_mode() if self.passivemode and \ (event == 'OnSecondChange' or event == 'OnMinuteChange'): return 0 self.start_script(script) self.balloon.hide_sstp_message() if event in self.boot_event: self.script_post_proc.append(self.surface_bootup) if communication is not None: self.script_post_proc.append(communication) return 1 def get_prefix(self): return self.prefix def stick_window(self, flag): self.surface.window_stick(flag) def toggle_bind(self, args): self.surface.toggle_bind(args) def get_menu_pixmap(self): path_background, path_sidebar, path_foreground = \ self.surface.get_menu_pixmap() top_dir = os.path.join(self.get_prefix(), 'ghost', 'master') if not os.path.exists(path_background): path = os.path.join(top_dir, 'menu_background.png') if os.path.exists(path): path_background = path else: path_background = None if not os.path.exists(path_sidebar): path = os.path.join(top_dir, 'menu_sidebar.png') if os.path.exists(path): path_sidebar = path else: path_sidebar = None if not os.path.exists(path_foreground): path = os.path.join(top_dir, 'menu_foreground.png') if os.path.exists(path): path_foreground = path else: path_foreground = None return path_background, path_sidebar, path_foreground def get_menu_fontcolor(self): return self.surface.get_menu_fontcolor() def get_mayuna_menu(self): return self.surface.get_mayuna_menu() def get_current_balloon_directory(self): return self.balloon.get_balloon_directory() def get_current_shell(self): return self.shell_directory def get_default_shell(self): default = self.shell_directory or 'master' if default not in self.shells: default = self.shells.keys()[0] # XXX return default def select_shell(self, shell_key): assert self.shells and shell_key in self.shells self.shell_directory = shell_key # save user's choice surface_name, surface_dir, surface_desc, surface_alias, surface, surface_tooltips = \ self.shells[shell_key].baseinfo def proc(self=self, key=shell_key): logging.info('ghost {0} {1}'.format(self.key, key)) self.set_surface(surface_desc, surface_alias, surface, surface_name, surface_dir, surface_tooltips) self.surface.reset_alignment() self.position_all() self.notify_event('OnShellChanged', surface_name, surface_name, surface_dir) self.enqueue_event('OnShellChanging', surface_name, surface_dir, proc=proc) def select_balloon(self, item, desc, balloon): self.balloon_directory = item # save user's choice if item == self.get_current_balloon_directory(): # no change return # need reloadning? assert item == balloon['balloon_dir'][0] path = os.path.join(get_ninix_home(), 'balloon', item) self.balloon.hide_all() self.set_balloon(desc, balloon) self.balloon.set_balloon_default() self.position_balloons() name = desc.get('name', '') logging.info('balloon {0} {1}'.format( name.encode('utf-8', 'ignore'), path)) self.notify_event('OnBalloonChange', name, path) def surface_bootup(self): for side in [0, 1]: if not self.__boot[side]: self.set_surface_default(side) self.surface.show(side) def get_uptime(self): uptime = int(time.time() - self.start_time) / 3600 if uptime < 0: self.start_time = time.time() return 0 return uptime def hide_all(self): self.surface.hide_all() self.balloon.hide_all() def position_balloons(self): self.surface.reset_balloon_position() def align_top(self, side): self.surface.set_alignment(side, 1) def align_bottom(self, side): self.surface.set_alignment(side, 0) def align_current(self): self.surface.set_alignment_current() def identify_window(self, win): return bool(self.surface.identify_window(win) or \ self.balloon.identify_window(win)) def set_surface_default(self, side=None): self.surface.set_surface_default(side) def get_surface_scale(self): return self.request_parent('GET', 'get_preference', 'surface_scale') def get_surface_size(self, side): result = self.surface.get_surface_size(side) return result if result is not None else (0, 0) def set_surface_position(self, side, x, y): self.surface.set_position(side, x, y) def set_surface_id(self, side, id): self.surface.set_surface(side, id) def get_surface_id(self, side): return self.surface.get_surface(side) def surface_is_shown(self, side): return bool(self.surface and self.surface.is_shown(side)) def get_kinoko_position(self, baseposition): side = 0 x, y = self.get_surface_position(side) w, h = self.get_surface_size(side) if baseposition == 1: rect = self.surface.get_collision_area(side, 'face') if rect is not None: x1, y1, x2, y2 = rect return x + (x2 - x1) / 2, y + (y2 - y1) / 2 else: return x + w / 2, y + h / 4 elif baseposition == 2: rect = self.surface.get_collision_area(side, 'bust') if rect is not None: x1, y1, x2, y2 = rect return x + (x2 - x1) / 2, y + (y2 - y1) / 2 else: return x + w / 2, y + h / 2 elif baseposition == 3: centerx, centery = self.surface.get_center(side) if centerx is None: centerx = w / 2 if centery is None: centery = h / 2 return x + centerx, y + centery else: # baseposition == 0 or baseposition not in [1, 2, 3]: # AKF centerx, centery = self.surface.get_kinoko_center(side) if centerx is None or centery is None: rect = self.surface.get_collision_area(side, 'head') if rect is not None: x1, y1, x2, y2 = rect return x + (x2 - x1) / 2, y + (y2 - y1) / 2 else: return x + w / 2, y + h / 8 return x + centerx, y + centery def raise_surface(self, side): self.surface.raise_(side) def lower_surface(self, side): self.surface.lower(side) def position_all(self): self.surface.reset_position() def raise_all(self): self.surface.raise_all() self.balloon.raise_all() def lower_all(self): self.surface.lower_all() self.balloon.lower_all() ### STARTER ### def stand_by(self, reset_surface): self.balloon.hide_all() self.balloon.hide_sstp_message() if reset_surface: self.set_surface_default() self.notify_event('OnSurfaceChange', self.get_surface_id(0), self.get_surface_id(1)) self.balloon.set_balloon_default() elif self.get_surface_id(0) != '0' or \ self.get_surface_id(1) != '10': self.__surface_life = random.randint(20, 30) ##logging.debug('surface_life = {0:d}'.format(self.__surface_life)) def start(self, key, init, temp, vanished, ghost_changed, prev_name): if self.is_running(): if temp: self.enter_temp_mode() else: if self.__temp_mode == 1: self.__temp_mode = 2 self.load_shiori() self.notify_start(init, vanished, ghost_changed, prev_name, '') return self.ghost_time = 0 self.vanished_count = 0 self.__running = 1 self.__temp_mode = temp self.key = key logging.info('ghost {0}'.format(key)) self.load_settings() shell_key = self.get_default_shell() self.shell_directory = shell_key # XXX assert self.shells and shell_key in self.shells surface_name, surface_dir, surface_desc, surface_alias, surface, surface_tooltips = \ self.shells[shell_key].baseinfo name = prev_name if ghost_changed else surface_name self.set_surface(surface_desc, surface_alias, surface, surface_name, surface_dir, surface_tooltips) balloon = None if not self.request_parent('GET', 'get_preference', 'ignore_default'): ## FIXME: change prefs key balloon_path = self.desc.get('deault.balloon.path', '') balloon_name = self.desc.get('balloon', '') if balloon_path: balloon = self.request_parent( 'GET', 'find_balloon_by_subdir', balloon_path) if balloon is None and balloon_name: balloon = self.request_parent( 'GET', 'find_balloon_by_name', balloon_name) if balloon is None: if self.balloon_directory is not None: balloon = self.balloon_directory else: balloon = self.request_parent( 'GET', 'get_preference', 'default_balloon') desc, balloon = self.request_parent( 'GET', 'get_balloon_description', balloon) self.set_balloon(desc, balloon) if not temp: self.load_shiori() self.restart() self.start_time = time.time() self.notify_start(init, vanished, ghost_changed, name, surface_dir) glib.timeout_add(10, self.do_idle_tasks) # 10[ms] def restart(self): self.load_history() self.vanished = 0 self.__boot = [0, 0] self.old_otherghostname = None ## FIXME self.reset_script(1) self.surface.reset_alignment() self.stand_by(1) self.position_all() self.reset_idle_time() self.__running = 1 def stop(self): if not self.__running: return self.notify_observer('finalize') self.__running = 0 self.save_settings() self.save_history() self.request_parent('NOTIFY', 'rebuild_ghostdb', self, None) self.hide_all() self.surface.finalize() self.balloon.finalize() if self.audio_player is not None: self.audio_player.set_state(gst.STATE_NULL) def process_script(self): now = time.time() idle = self.get_idle_time() minute, second = time.localtime(now)[4:6] if self.clock[0] != second: ## FIXME if not self.__temp_mode: self.ghost_time += 1 self.request_parent( 'NOTIFY', 'rebuild_ghostdb', self, self.get_selfname(), self.get_surface_id(0), self.get_surface_id(1)) otherghostname = self.request_parent( 'GET', 'get_otherghostname', self.get_selfname()) if otherghostname != self.old_otherghostname: args = [] args.extend(otherghostname) args.insert(0, 'otherghostname') args = tuple(args) keyword = {'event_type': 'NOTIFY'} self.notify_event(*args, **keyword) self.old_otherghostname = otherghostname if not self.__running: pass elif self.script_mode == self.PAUSE_MODE: ##if idle > self.PAUSE_TIMEOUT: ## self.script_mode = self.BROWSE_MODE pass elif self.processed_script or self.processed_text: self.interpret_script() elif self.script_post_proc: for proc in self.script_post_proc: proc() self.script_post_proc = [] elif self.script_mode == self.SELECT_MODE: if self.passivemode: pass elif idle > self.SELECT_TIMEOUT: self.script_mode = self.BROWSE_MODE if self.sstp_request_handler: self.sstp_request_handler.send_timeout() self.sstp_request_handler = None if not self.notify_event('OnChoiceTimeout'): self.stand_by(0) elif self.sstp_handle is not None: self.close_sstp_handle() elif self.balloon.user_interaction: pass elif idle > self.__balloon_life > 0 and not self.passivemode: self.__balloon_life = 0 self.stand_by(0) self.notify_event('OnBalloonClose', self.__current_script) if self.request_parent('GET', 'get_preference', 'sink_after_talk'): self.surface.lower_all() elif self.event_queue and self.handle_event(): pass elif self.script_queue and not self.passivemode: if self.get_silent_time() > 0: self.keep_silence(True) # extend silent time script, sender, self.sstp_handle, \ host, show_sstp_marker, use_translator, \ self.sstp_entry_db, self.sstp_request_handler = \ self.script_queue.pop(0) if self.cantalk: if show_sstp_marker: self.balloon.show_sstp_message(sender, host) else: self.balloon.hide_sstp_message() # XXX: how about the use_translator flag? self.start_script(script, self.FROM_SSTP_CLIENT) elif self.get_silent_time() > 0: if now - self.get_silent_time() > self.SILENT_TIME: self.keep_silence(False) elif self.clock[0] != second and \ self.notify_event('OnSecondChange', self.get_uptime(), self.surface.get_mikire(), self.surface.get_kasanari(), not self.passivemode and self.cantalk): pass elif self.clock[1] != minute and \ self.notify_event('OnMinuteChange', self.get_uptime(), self.surface.get_mikire(), self.surface.get_kasanari(), not self.passivemode and self.cantalk): pass elif self.surface_mouse_motion is not None: side, x, y, part = self.surface_mouse_motion self.notify_event('OnMouseMove', x, y, '', side, part) self.surface_mouse_motion = None elif idle > self.__surface_life > 0 and not self.passivemode: self.__surface_life = 0 self.notify_event('OnSurfaceRestore', self.get_surface_id(0), self.get_surface_id(1)) self.clock = (second, minute) reload_event = None def do_idle_tasks(self): if not self.__running: return False if self.__temp_mode: self.process_script() if not self.busy() and \ not self.script_queue and \ not (self.processed_script or \ self.processed_text): if self.__temp_mode == 1: time.sleep(1.4) self.finalize() self.request_parent('NOTIFY', 'close_ghost', self) self.request_parent('NOTIFY', 'reset_sstp_flag') return False else: self.request_parent('NOTIFY', 'reset_sstp_flag') self.leave_temp_mode() return True else: return True if self.reload_event and not self.busy() and \ not (self.processed_script or self.processed_text): self.hide_all() logging.info('reloading....') self.shiori.unload() self.updateman.clean_up() # Don't call before unloading SHIORI self.request_parent( 'NOTIFY', 'stop_sakura', self, lambda a: self.request_parent( 'NOTIFY', 'reload_current_sakura', a), (self)) self.load_settings() self.restart() ## FIXME logging.info('done.') self.enqueue_event(*self.reload_event) self.reload_event = None # continue network update (enqueue events) if self.updateman.is_active(): self.updateman.run() while 1: event = self.updateman.get_event() if not event: break if event[0] == 'OnUpdateComplete' and event[1] == 'changed': self.reload_event = event else: self.enqueue_event(*event) self.process_script() return True def quit(self): self.request_parent('NOTIFY', 'stop_sakura', self) ### SCRIPT PLAYER ### def start_script(self, script, origin=None): if not script: return self.script_origin = origin or self.FROM_GHOST self.reset_script(1) self.__current_script = script if not script.rstrip().endswith(r'\e'): script = ''.join((script, r'\e')) self.processed_script = [] while 1: try: self.processed_script.extend(self.script_parser.parse(script)) except ninix.script.ParserError as e: logging.error('-' * 50) logging.error('{0}'.format(e)) # 'UTF-8' done, script = e self.processed_script.extend(done) else: break self.script_mode = self.BROWSE_MODE self.script_wait = None self.script_side = 0 self.time_critical_session = 0 self.quick_session = 0 self.set_synchronized_session(reset=1) self.balloon.hide_all() self.balloon.clear_text_all() self.balloon.set_balloon_default() self.current_time = time.localtime(time.time()) self.reset_idle_time() if self.request_parent('GET', 'get_preference', 'raise_before_talk'): self.raise_all() def __yen_e(self, args): surface_id = self.get_surface_id(self.script_side) self.surface.invoke_yen_e(self.script_side, surface_id) self.reset_script() self.__balloon_life = self.BALLOON_LIFE def __yen_0(self, args): ##self.balloon.show(0) self.script_side = 0 def __yen_1(self, args): ##self.balloon.show(1) self.script_side = 1 def __yen_p(self, args): try: chr_id = int(args[0]) except: return if chr_id >= 0: self.script_side = chr_id def __yen_4(self, args): if self.script_side == 0: sw, sh = self.get_surface_size(1) sx, sy = self.get_surface_position(1) elif self.script_side == 1: sw, sh = self.get_surface_size(0) sx, sy = self.get_surface_position(0) else: return w, h = self.get_surface_size(self.script_side) x, y = self.get_surface_position(self.script_side) left, top, scrn_w, scrn_h = ninix.pix.get_workarea() if sx + sw / 2 > left + scrn_w / 2: new_x = min(x - scrn_w // 20, sx - scrn_w // 20) else: new_x = max(x + scrn_w // 20, sx + scrn_w // 20) step = -10 if x > new_x else 10 for current_x in range(x, new_x, step): self.set_surface_position(self.script_side, current_x, y) self.set_surface_position(self.script_side, new_x, y) def __yen_5(self, args): if self.script_side == 0: sw, sh = self.get_surface_size(1) sx, sy = self.get_surface_position(1) elif self.script_side == 1: sw, sh = self.get_surface_size(0) sx, sy = self.get_surface_position(0) else: return w, h = self.get_surface_size(self.script_side) x, y = self.get_surface_position(self.script_side) left, top, scrn_w, scrn_h = ninix.pix.get_workarea() if x < sx + sw / 2 < x + w or sx < x + w / 2 < sx + sw: return if sx + sw / 2 > x + w / 2: new_x = sx - w / 2 + 1 else: new_x = sx + sw - w / 2 - 1 new_x = max(new_x, left) new_x = min(new_x, left + scrn_w - w) step = -10 if x > new_x else 10 for current_x in range(x, new_x, step): self.set_surface_position(self.script_side, current_x, y) self.set_surface_position(self.script_side, new_x, y) def __yen_s(self, args): surface_id = args[0] if surface_id == '-1': self.surface.hide(self.script_side) else: self.set_surface_id(self.script_side, surface_id) self.surface.show(self.script_side) ## FIXME: self.script_side > 1 self.notify_event('OnSurfaceChange', self.get_surface_id(0), self.get_surface_id(1)) if self.script_side in [0, 1] and not self.__boot[self.script_side]: self.__boot[self.script_side] = 1 def __yen_b(self, args): if args[0] == '-1': self.balloon.hide(self.script_side) else: try: balloon_id = int(args[0]) / 2 except ValueError: balloon_id = 0 else: self.balloon.set_balloon(self.script_side, balloon_id) def __yen__b(self, args): try: filename, x, y = self.expand_meta(args[0]).split(',') except: filename, param = self.expand_meta(args[0]).split(',') assert param == 'inline' x, y = 0, 0 ## FIXME filename = get_normalized_path(filename) path = os.path.join(self.get_prefix(), 'ghost/master', filename) if os.path.isfile(path): self.balloon.append_image(self.script_side, path, x, y) else: path = ''.join((path, '.png')) if os.path.isfile(path): self.balloon.append_image(self.script_side, path, x, y) def __yen_n(self, args): if args and self.expand_meta(args[0]) == 'half': self.balloon.append_text(self.script_side, u'\n[half]') else: self.balloon.append_text(self.script_side, u'\n') def __yen_c(self, args): self.balloon.clear_text(self.script_side) def __set_weight(self, value, unit): try: amount = int(value) * unit - 0.01 except ValueError: amount = 0 if amount > 0: self.script_wait = time.time() + amount def __yen_w(self, args): script_speed = self.request_parent( 'GET', 'get_preference', 'script_speed') if not self.quick_session and script_speed >= 0: self.__set_weight(args[0], 0.05) # 50[ms] def __yen__w(self, args): script_speed = self.request_parent( 'GET', 'get_preference', 'script_speed') if not self.quick_session and script_speed >= 0: self.__set_weight(args[0], 0.001) # 1[ms] def __yen_t(self, args): self.time_critical_session = not self.time_critical_session def __yen__q(self, args): self.quick_session = not self.quick_session def __yen__s(self, args): self.set_synchronized_session([int(arg) for arg in args]) def __yen__e(self, args): self.balloon.hide(self.script_side) self.balloon.clear_text(self.script_side) def __yen_q(self, args): newline_required = 0 if len(args) == 3: # traditional syntax num, link_id, text = args newline_required = 1 else: # new syntax text, link_id = args text = self.expand_meta(text) self.balloon.append_link(self.script_side, link_id, text, newline_required) self.script_mode = self.SELECT_MODE def __yen_URL(self, args): text = self.expand_meta(args[0]) if len(args) == 1: link = text else: link = '#cancel' self.balloon.append_link(self.script_side, link, text) for i in range(1, len(args), 2): link = self.expand_meta(args[i]) text = self.expand_meta(args[i + 1]) self.balloon.append_link(self.script_side, link, text) self.script_mode = self.SELECT_MODE def __yen__a(self, args): if self.anchor: anchor_id = self.anchor[0] text = self.anchor[1] self.balloon.append_link_out(self.script_side, anchor_id, text) self.anchor = None else: anchor_id = args[0] self.anchor = [('anchor', anchor_id), ''] self.balloon.append_link_in(self.script_side, self.anchor[0]) def __yen_x(self, args): if self.script_mode == self.BROWSE_MODE: self.script_mode = self.PAUSE_MODE def __yen_a(self, args): self.start_script(self.getaistringrandom()) def __yen_i(self, args): try: actor_id = int(args[0]) except ValueError: pass else: self.surface.invoke(self.script_side, actor_id) def __yen_j(self, args): jump_id = args[0] if self.is_URL(jump_id): webbrowser.open(jump_id) elif self.sstp_entry_db: self.start_script(self.sstp_entry_db.get(jump_id, r'\e')) def __yen_minus(self, args): self.quit() def __yen_plus(self, args): self.request_parent('NOTIFY', 'select_ghost', self, 1) def __yen__plus(self, args): self.request_parent('NOTIFY', 'select_ghost', self, 0) def __yen_m(self, args): self.write_sstp_handle(self.expand_meta(args[0])) def __yen_and(self, args): if self.name2codepoint is not None: try: text = unichr(self.name2codepoint.get(args[0])) except: text = None else: text = None if text is None: text = '?' self.balloon.append_text(self.script_side, text) def __yen__m(self, args): try: num = int(args[0], 16) except ValueError: num = 0 if 0x20 <= num <= 0x7e: text = chr(num) else: text = '?' self.balloon.append_text(self.script_side, text) def __yen__u(self, args): if re.match('0x[a-fA-F0-9]{4}', args[0]): text = eval(''.join(('u"\\u', args[0][2:], '"'))) self.balloon.append_text(self.script_side, text) else: self.balloon.append_text(self.script_side, u'?') def __yen__v(self, args): if self.audio_player is None: return filename = self.expand_meta(args[0]) filename = get_normalized_path(filename) path = os.path.join(self.get_prefix(), 'ghost/master', filename) if os.path.isfile(path): self.audio_player.set_state(gst.STATE_NULL) self.audio_player.set_property( 'uri', 'file://' + urllib.quote(path)) self.audio_player.set_state(gst.STATE_PLAYING) def __yen_exclamation(self, args): ## FIXME if not args: return argc = len(args) args = [self.expand_meta(s) for s in args] if args[0] == 'raise' and argc >= 2: self.notify_event(*args[1:10]) elif args[0:2] == ['open', 'browser'] and argc > 2: webbrowser.open(args[2]) elif args[0:2] == ['open', 'communicatebox']: if not self.passivemode: self.balloon.open_communicatebox() elif args[0:2] == ['open', 'teachbox']: if not self.passivemode: self.balloon.open_teachbox() elif args[0:2] == ['open', 'inputbox'] and argc > 2: if not self.passivemode: if argc > 4: self.balloon.open_inputbox(args[2], args[3], args[4]) elif argc == 4: self.balloon.open_inputbox(args[2], args[3]) else: self.balloon.open_inputbox(args[2]) elif args[0:2] == ['open', 'configurationdialog']: self.request_parent('NOTIFY', 'edit_preferences') elif args[0:2] == ['change', 'shell'] and argc > 2: for key in self.shells: shell_name = self.shells[key].baseinfo[0] if shell_name == args[2]: self.select_shell(key) break elif args[0:2] == ['change', 'ghost'] and argc > 2: if args[2] == 'random': self.request_parent('NOTIFY', 'select_ghost', self, 0, 0) else: self.request_parent( 'NOTIFY', 'select_ghost_by_name', self, args[2], 0) elif args[0:1] == ['updatebymyself']: if not self.busy(check_updateman=False): self.__update() elif args[0:1] == ['vanishbymyself']: self.vanished = 1 ## FIXME self.vanish_by_myself() elif args[1:2] == ['repaint']: if args[0:1] == ['lock']: self.lock_repaint = 1 elif args[0:1] == ['unlock']: self.lock_repaint = 0 elif args[1:2] == ['passivemode']: if args[0:1] == ['enter']: self.passivemode = 1 elif args[0:1] == ['leave']: self.passivemode = 0 elif args[0:2] == ['set', 'alignmentondesktop']: if args[2] == 'bottom': if self.synchronized_session: for chr_id in self.synchronized_session: self.align_bottom(chr_id) else: self.align_bottom(self.script_side) elif args[2] == 'top': if self.synchronized_session: for chr_id in self.synchronized_session: self.align_top(chr_id) else: self.align_top(self.script_side) elif args[0:2] == ['set', 'alignmenttodesktop']: if args[2] == 'free': if self.synchronized_session: for chr_id in self.synchronized_session: self.surface.set_alignment(chr_id, 2) else: self.surface.set_alignment(self.script_side, 2) elif args[0:2] == ['set', 'windowstate']: if args[2] == 'minimize': self.surface.window_iconify(True) ##elif args[2] == '!minimize': ## self.surface.window_iconify(False) elif args[2] == 'stayontop': self.surface.window_stayontop(True) elif args[2] == '!stayontop': self.surface.window_stayontop(False) elif args[0:2] == ['set', 'wallpaper']: path = os.path.join(self.get_prefix(), args[2]) opt = None if len(args) > 3: # possible picture_options value: # "none", "wallpaper", "centered", "scaled", "stretched" options = { 'center': 'centered', 'tile': 'wallpaper', 'stretch': 'stretched' } if args[3] not in options: opt = None else: opt = options[args[3]] if opt is None: opt = 'centered' # default if os.path.exists(path) and gconf is not None: client = gconf.client_get_default() gconf_background_dir = '/desktop/gnome/background/' client.set_string(''.join((gconf_background_dir, 'picture_filename')), path) client.set_string(''.join((gconf_background_dir, 'picture_options')), opt) elif args[0] == '*': self.balloon.append_sstp_marker(self.script_side) else: pass ## FIXME def __yen___c(self, args): self.balloon.open_communicatebox() def __yen___t(self, args): self.balloon.open_teachbox() def __yen_v(self, args): self.raise_surface(self.script_side) def __yen_f(self, args): if len(args) != 2: ## FIXME return tag = None if args[0] == 'sup': tag = '' if args[1] == 'true' else '' elif args[0] == 'sub': tag = '' if args[1] == 'true' else '' elif args[0] == 'strike': tag = '' if args[1] in ['true', '1', 1] else '' elif args[0] == 'underline': tag = '' if args[1] in ['true', '1', 1] else '' else: pass ## FIXME if tag is not None: self.balloon.append_meta(self.script_side, tag) __script_tag = { r'\e': __yen_e, r'\0': __yen_0, r'\h': __yen_0, r'\1': __yen_1, r'\u': __yen_1, r'\p': __yen_p, r'\4': __yen_4, r'\5': __yen_5, r'\s': __yen_s, r'\b': __yen_b, r'\_b': __yen__b, r'\n': __yen_n, r'\c': __yen_c, r'\w': __yen_w, r'\_w': __yen__w, r'\t': __yen_t, r'\_q': __yen__q, r'\_s': __yen__s, r'\_e': __yen__e, r'\q': __yen_q, r'\URL': __yen_URL, r'\_a': __yen__a, r'\x': __yen_x, r'\a': __yen_a, # Obsolete: only for old SHIORI r'\i': __yen_i, r'\j': __yen_j, r'\-': __yen_minus, r'\+': __yen_plus, r'\_+': __yen__plus, r'\m': __yen_m, r'\&': __yen_and, r'\_m': __yen__m, r'\_u': __yen__u, r'\_v': __yen__v, r'\!': __yen_exclamation, r'\__c': __yen___c, r'\__t': __yen___t, r'\v': __yen_v, r'\f': __yen_f, } def interpret_script(self): if self.script_wait is not None: if time.time() < self.script_wait: return self.script_wait = None if self.processed_text: self.balloon.show(self.script_side) self.balloon.append_text(self.script_side, self.processed_text[0]) self.processed_text = self.processed_text[1:] surface_id = self.get_surface_id(self.script_side) count = self.balloon.get_text_count(self.script_side) if self.surface.invoke_talk(self.script_side, surface_id, count): self.balloon.reset_text_count(self.script_side) script_speed = self.request_parent( 'GET', 'get_preference', 'script_speed') if script_speed > 0: self.script_wait = time.time() + script_speed * 0.02 return node = self.processed_script.pop(0) if node[0] == ninix.script.SCRIPT_TAG: name, args = node[1], node[2:] if name in self.__script_tag: self.__script_tag[name](self, args) else: pass ## FIMXE elif node[0] == ninix.script.SCRIPT_TEXT: text = self.expand_meta(node[1]) if self.anchor: self.anchor[1] = ''.join((self.anchor[1], text)) script_speed = self.request_parent( 'GET', 'get_preference', 'script_speed') if not self.quick_session and script_speed >= 0: self.processed_text = text else: self.balloon.append_text(self.script_side, text) def reset_script(self, reset_all=0): if reset_all: self.script_mode = self.BROWSE_MODE self.script_post_proc = [] self.__current_script = '' self.processed_script = None self.processed_text = '' self.time_critical_session = 0 self.quick_session = 0 self.set_synchronized_session(reset=1) self.reset_idle_time() def set_synchronized_session(self, list=[], reset=0): if reset: self.synchronized_session = [] elif not list: if self.synchronized_session: self.synchronized_session = [] else: self.synchronized_session = [0, 1] else: self.synchronized_session = list self.balloon.synchronize(self.synchronized_session) def expand_meta(self, text_node): buf = [] for chunk in text_node: if chunk[0] == ninix.script.TEXT_STRING: buf.append(chunk[1]) elif chunk[1] == '%month': buf.append(str(self.current_time[1])) elif chunk[1] == '%day': buf.append(str(self.current_time[2])) elif chunk[1] == '%hour': buf.append(str(self.current_time[3])) elif chunk[1] == '%minute': buf.append(str(self.current_time[4])) elif chunk[1] == '%second': buf.append(str(self.current_time[5])) elif chunk[1] in ['%username', '%c']: buf.append(self.get_username()) elif chunk[1] == '%selfname': buf.append(self.get_selfname()) elif chunk[1] == '%selfname2': buf.append(self.get_selfname2()) elif chunk[1] == '%keroname': buf.append(self.get_keroname()) elif chunk[1] == '%friendname': buf.append(self.get_friendname()) elif chunk[1] == '%screenwidth': left, top, scrn_w, scrn_h = ninix.pix.get_workarea() buf.append(str(scrn_w)) elif chunk[1] == '%screenheight': left, top, scrn_w, scrn_h = ninix.pix.get_workarea() buf.append(str(scrn_h)) elif chunk[1] == '%et': buf.append( unicode('%d万年' % self.current_time[7], 'utf-8')) elif chunk[1] == '%exh': buf.append(str(self.get_uptime())) elif chunk[1] in ['%ms', '%mz', '%ml', '%mc', '%mh', \ '%mt', '%me', '%mp', '%m?']: buf.append( self.getword(''.join(('\\', chunk[1][1:])))) elif chunk[1] == '%dms': buf.append(self.getdms()) else: # %c, %songname buf.append(chunk[1]) return ''.join(buf) ### SEND SSTP/1.3 ### def _send_sstp_handle(self, data): r, w, e = select.select([], [self.sstp_handle], [], 0) if not w: return try: self.sstp_handle.send(''.join((data, '\n'))) except socket.error: pass def write_sstp_handle(self, data): if self.sstp_handle is None: return self._send_sstp_handle(''.join(('+', data))) ##logging.debug('write_sstp_handle({0})'.format(repr(data))) def close_sstp_handle(self): if self.sstp_handle is None: return self._send_sstp_handle('-') ##logging.debug('close_sstp_handle()') try: self.sstp_handle.close() except socket.error: pass self.sstp_handle = None def close(self): if self.busy(): gtk.gdk.beep() ## FIXME return self.reset_script(1) self.enqueue_event('OnClose') def about(self): if self.busy(): gtk.gdk.beep() ## FIXME return self.start_script(ninix.version.VERSION_INFO) self.balloon.hide_sstp_message() def __update(self): if self.updateman.is_active(): return homeurl = self.getstring('homeurl') if not homeurl: self.start_script( ''.join((r'\t\h\s[0]', unicode(_("I'm afraid I don't have Network Update yet."), 'utf-8'), r'\e'))) self.balloon.hide_sstp_message() return ghostdir = self.get_prefix() logging.info('homeurl = {0}'.format(homeurl)) logging.info('ghostdir = {0}'.format(ghostdir)) self.updateman.start(homeurl, ghostdir) def network_update(self): if self.busy(): gtk.gdk.beep() ## FIXME return self.__update() class VanishDialog(object): def __init__(self): self.request_parent = lambda *a: None # dummy self.window = gtk.Dialog() self.window.connect('delete_event', self.cancel) self.window.set_title('Vanish') self.window.set_modal(True) self.window.set_position(gtk.WIN_POS_CENTER) self.label = gtk.Label(unicode(_('Vanish'), 'utf-8')) self.window.vbox.pack_start(self.label, padding=10) self.label.show() box = gtk.HButtonBox() box.set_layout(gtk.BUTTONBOX_END) self.window.action_area.pack_start(box) box.show() button = gtk.Button(unicode(_('Yes'), 'utf-8')) button.connect('clicked', self.ok) box.add(button) button.show() button = gtk.Button(unicode(_('No'), 'utf-8')) button.connect('clicked', self.cancel) box.add(button) button.show() def set_responsible(self, request_method): self.request_parent = request_method def set_message(self, message): self.label.set_text(message) def show(self): self.window.show() def ok(self, widget, event=None): self.window.hide() self.request_parent('NOTIFY', 'notify_vanish_selected') return True def cancel(self, widget, event=None): self.window.hide() self.request_parent('NOTIFY', 'notify_vanish_canceled') return True def test(): pass if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/script.py000066400000000000000000000510751172114553600172120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # script.py - a Sakura Script parser # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2004-2012 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. # import re import sys TOKEN_TAG = 1 TOKEN_META = 2 TOKEN_OPENED_SBRA = 3 TOKEN_CLOSED_SBRA = 4 TOKEN_NUMBER = 5 TOKEN_STRING = 6 patterns = [ (TOKEN_TAG, re.compile(r'\\[ehunjcxtqzy*v0123456789fmia!&+---]|' r'\\[sbp][0-9]?|\\w[0-9]|\\_[wqslvVbe+cumna]|' r'\\__[ct]|\\URL')), (TOKEN_META, re.compile(r'%month|%day|%hour|%minute|%second|%username|' r'%selfname2?|%keroname|%friendname|%songname|' r'%screen(width|height)|%exh|%et|%m[szlchtep?]|' r'%dms|%j|%c')), (TOKEN_NUMBER, re.compile(r'[0-9]+')), (TOKEN_OPENED_SBRA, re.compile(r'\[')), (TOKEN_CLOSED_SBRA, re.compile(r'\]')), (TOKEN_STRING, re.compile(r'(\\\\|\\%|\\\]|[^\\\[\]%0-9])+')), (TOKEN_STRING, re.compile(r'[%\\]')), ] SCRIPT_TAG = 1 SCRIPT_TEXT = 2 TEXT_META = 1 TEXT_STRING = 2 class ParserError(Exception): def __init__(self, message, error='strict', script=None, src=None, column=None, length=None, skip=None): if error not in ['strict', 'loose']: raise ValueError, 'unknown error scheme: {0}'.format(str(error)) self.message = message self.error = error self.script = script or [] self.src = src or '' self.column = column self.length = length or 0 self.skip = skip or 0 def __getitem__(self, n): if n == 0: if self.error == 'strict': return [] else: return self.script elif n == 1: if self.error == 'strict' or self.column is None: return '' else: return self.src[self.column + self.skip:] else: raise IndexError('tuple index out of range') def __str__(self): if self.column is not None: column = self.column if self.src: dump = ''.join((self.src[:column], '\x1b[7m', (self.src[column:column + self.length] or ' '), '\x1b[m', self.src[column + self.length:])) else: dump = '' else: column = '??' dump = self.src return 'ParserError: column {0}: {1}\n{2}'.format(column, self.message, dump.encode('utf-8')) class Parser(object): def __init__(self, error='strict'): if error not in ['strict', 'loose']: raise ValueError, 'unknown error scheme: {0}'.format(str(error)) self.error = error def perror(self, msg, position='column', skip=None): if position not in ['column', 'eol']: raise ValueError, 'unknown position scheme: {0}'.format(str(position)) if skip not in ['length', 'rest', None]: raise ValueError, 'unknown skip scheme: {0}'.format(str(skip)) if position == 'column': column = self.column length = self.length if skip == 'length': skip = length elif skip == 'rest': skip = len(self.src[column:]) else: skip = 0 else: column = len(self.src) length = 0 skip = 0 return ParserError(msg, self.error, self.script, self.src, column, length, skip) def tokenize(self, s): tokens = [] pos = 0 end = len(s) while pos < end: for token, pattern in patterns: match = pattern.match(s, pos) if match: break else: raise RuntimeError, 'should not reach here' tokens.append((token, s[pos:match.end()])) pos = match.end() return tokens def next_token(self): try: token, lexeme = self.tokens.pop(0) except IndexError: raise self.perror('unexpected end of script', position='eol') self.column += self.length self.length = len(lexeme) return token, lexeme def parse(self, s): if not s: return [] # tokenize the script self.src = s self.tokens = self.tokenize(self.src) self.column = 0 self.length = 0 # parse the sequence of tokens self.script = [] text = [] string_chunks = [] scope = 0 anchor = None while self.tokens: token, lexeme = self.next_token() if token == TOKEN_STRING and lexeme == '\\': if string_chunks: text.append((TEXT_STRING, ''.join(string_chunks))) if text: self.script.append((SCRIPT_TEXT, tuple(text))) raise self.perror('unknown tag', skip='length') elif token == TOKEN_STRING and lexeme == '%': string_chunks.append(lexeme) text.append((TEXT_STRING, ''.join(string_chunks))) self.script.append((SCRIPT_TEXT, tuple(text))) raise self.perror('unknown meta string', skip='length') if token in [TOKEN_NUMBER, TOKEN_OPENED_SBRA, TOKEN_STRING, TOKEN_CLOSED_SBRA]: lexeme = lexeme.replace(r'\\', '\\') lexeme = lexeme.replace(r'\%', '%') string_chunks.append(lexeme) continue if string_chunks: text.append((TEXT_STRING, ''.join(string_chunks))) string_chunks = [] if token == TOKEN_META: if lexeme == '%j': argument = self.read_sbra_id() text.append((TEXT_META, lexeme, argument)) else: text.append((TEXT_META, lexeme)) continue if text: self.script.append((SCRIPT_TEXT, tuple(text))) text = [] if lexeme in ['\\a', '\\c', '\\e', '\\t', '\\_e', '\\v', '\\x', '\\y', '\\z', '\\_q', '\\4', '\\5', '\\6', '\\7', '\\2', '\\*', '\\-', '\\+', '\\_+', '\\_n', '\\_V', '\\__c', '\\__t']: self.script.append((SCRIPT_TAG, lexeme)) elif lexeme in ['\\0', '\\h']: self.script.append((SCRIPT_TAG, lexeme)) scope = 0 elif lexeme in ['\\1', '\\u']: self.script.append((SCRIPT_TAG, lexeme)) scope = 1 elif lexeme in ['\\s', '\\b', '\\p']: argument = self.read_sbra_id() self.script.append((SCRIPT_TAG, lexeme, argument)) elif lexeme.startswith('\\s') or \ lexeme.startswith('\\b') or \ lexeme.startswith('\\p') or \ lexeme.startswith('\\w'): num = lexeme[2] if lexeme.startswith('\\s') and scope == 1: num = str(int(num) + 10) self.script.append((SCRIPT_TAG, lexeme[:2], num)) elif lexeme in ['\\_w']: argument = self.read_sbra_number() self.script.append((SCRIPT_TAG, lexeme, argument)) elif lexeme in ['\\i', '\\j', '\\&', '\\_u', '\\_m']: argument = self.read_sbra_id() self.script.append((SCRIPT_TAG, lexeme, argument)) elif lexeme in ['\\_b', '\\_c', '\\_l', '\\_v', '\\m', '\\3', '\\8', '\\9']: argument = self.read_sbra_text() self.script.append((SCRIPT_TAG, lexeme, argument)) elif lexeme in ['\\n']: if self.tokens and self.tokens[0][0] == TOKEN_OPENED_SBRA: argument = self.read_sbra_text() self.script.append((SCRIPT_TAG, lexeme, argument)) else: self.script.append((SCRIPT_TAG, lexeme)) elif lexeme in ['\\URL']: buf = [self.read_sbra_text()] while self.tokens and self.tokens[0][0] == TOKEN_OPENED_SBRA: buf.append(self.read_sbra_text()) buf.append(self.read_sbra_text()) self.script.append((SCRIPT_TAG, lexeme) + tuple(buf)) elif lexeme in ['\\!']: args = self.split_params(self.read_sbra_text()) self.script.append((SCRIPT_TAG, lexeme) + tuple(args)) elif lexeme in ['\\q']: if self.tokens and self.tokens[0][0] == TOKEN_OPENED_SBRA: args = self.split_params(self.read_sbra_text()) if len(args) != 2: raise self.perror('wrong number of arguments', skip='length') if len(args[1]) != 1 or not args[1][0][1]: raise self.perror('syntax error (expected an ID)', skip='length') arg1 = args[0] arg2 = args[1][0][1] self.script.append((SCRIPT_TAG, lexeme, arg1, arg2)) else: arg1 = self.read_number() arg2 = self.read_sbra_id() arg3 = self.read_sbra_text() self.script.append((SCRIPT_TAG, lexeme, arg1, arg2, arg3)) elif lexeme in ['\\_s']: if self.tokens and self.tokens[0][0] == TOKEN_OPENED_SBRA: args = [arg[0][1] for arg in \ self.split_params(self.read_sbra_text())] self.script.append((SCRIPT_TAG, lexeme) + tuple(args)) else: self.script.append((SCRIPT_TAG, lexeme)) elif lexeme in ['\\_a']: if anchor is None: anchor = self.perror(r'syntax error (unbalanced \_a tag)', skip='rest') self.script.append( (SCRIPT_TAG, lexeme, self.read_sbra_id())) else: anchor = None self.script.append((SCRIPT_TAG, lexeme)) elif lexeme in ['\\f']: args = [arg[0][1] for arg in \ self.split_params(self.read_sbra_text())] self.script.append((SCRIPT_TAG, lexeme) + tuple(args)) else: raise self.perror('unknown tag ({0})'.format(lexeme), skip='length') if anchor: if self.script[-1] == (SCRIPT_TAG, r'\e'): self.script.insert(len(self.script) - 1, (SCRIPT_TAG, r'\_a')) else: self.script.append((SCRIPT_TAG, r'\_a')) anchor.script=self.script raise anchor if string_chunks: text.append((TEXT_STRING, ''.join(string_chunks))) if text: self.script.append((SCRIPT_TEXT, tuple(text))) return self.script def read_number(self): token, number = self.next_token() if token != TOKEN_NUMBER: raise self.perror('syntax error (expected a number)') return number def read_sbra_number(self): token, lexeme = self.next_token() if token != TOKEN_OPENED_SBRA: raise self.perror('syntax error (expected a square bracket)') token, number = self.next_token() if token != TOKEN_NUMBER: raise self.perror('syntax error (expected a number)', skip='length') token, lexeme = self.next_token() if token != TOKEN_CLOSED_SBRA: raise self.perror('syntax error (expected a square bracket)', skip='length') return number def read_sbra_id(self): text = self.read_sbra_text() if len(text) != 1: raise self.perror('syntax error (expected a single ID)', skip='length') try: sbra_id = str(int(text[0][1])) except: pass else: return sbra_id return text[0][1] def read_sbra_text(self): token, lexeme = self.next_token() if token != TOKEN_OPENED_SBRA: raise self.perror('syntax error (expected a square bracket)') text = [] string_chunks = [] while self.tokens: token, lexeme = self.next_token() if token in [TOKEN_NUMBER, TOKEN_STRING, TOKEN_OPENED_SBRA, TOKEN_TAG]: lexeme = lexeme.replace(r'\\', '\\') lexeme = lexeme.replace(r'\%', '%') lexeme = lexeme.replace(r'\]', ']') string_chunks.append(lexeme) continue if string_chunks: text.append((TEXT_STRING, ''.join(string_chunks))) string_chunks = [] if token == TOKEN_CLOSED_SBRA: break elif token == TOKEN_META: text.append((TEXT_META, lexeme)) else: raise self.perror('syntax error (wrong type of argument)', skip='length') else: raise self.perror('unexpected end of script', position='eol') return tuple(text) re_param = re.compile('("[^"]*"|[^,])*') re_quote = re.compile('"([^"]*)"') def split_params(self, text): params = [] buf = [] for token, lexeme in text: i = 0 j = len(lexeme) if token == TEXT_STRING: while i < j: match = self.re_param.match(lexeme, i) if not match: break param, n = self.re_quote.subn( lambda m: m.group(1), match.group()) if param or not buf: buf.append((token, param)) params.append(tuple(buf)) buf = [] i = match.end() if i < j: assert lexeme[i] == ',' i += 1 if i < j: buf.append((token, lexeme[i:])) if buf: params.append(tuple(buf)) return params # Tests testcases = [ # legal cases r'\s[4]ちゃんと選んでよう〜っ。\w8\uまあ、ユーザさんも忙しいんやろ‥‥\e', r'%selfnameと%keroname\e', r'エスケープのテスト \\, \%, [, ], \] どーかな?\e', r'\j[http://www.asahi.com]\e', r'\j[http://www.asahi.com/[escape\]/\%7Etest]\e', r'\j[http://www.asahi.com/%7Etest/]\e', r'\h\s[0]%usernameさんは今どんな感じ?\n\n\q0[#temp0][まあまあ]\q1[#temp1][今ひとつ]\z', r'\q0[#temp0][今日は%month月%day日だよ]\e', r'\q0[#cancel][行かない]\q1[http://www.asahi.com/%7Etest/][行く]\e', r'\q[テスト,test]\q[%month月%day日,date]\e', r'\q[テスト,http://www.asahi.com/]\e', r'\q[テスト,http://www.asahi.com/%7Etest/]\e', r'\h\s[0]%j[#temp0]\e', r'\URL[http://www.asahi.com/]\e', r'\URL[http://www.asahi.com/%7Etest/]\e', r'\URL[行かない][http://www.asahi.com/][トップ][http://www.asahi.com/%7Etest/][テスト]\e', r'\_s\s5\w44えんいー%c\e', r'\h%m?\e', r'\URL[http://www.foo.jp/%7Ebar/]', r'\b[0]\b[normal]\i[0]\i[eyeblink]', r'\c\x\t\_q\*\1\2\4\5\-\+\_+\a\__c\__t\_n', r'\_l[0,0]\_v[test.wav]\_V\_c[test]', r'\h\s0123\u\s0123\h\s1234\u\s1234', r'\s[-1]\b[-1]', r'\_u[0x0010]\_m[0x01]\&[Uuml]\&[uuml]', r'\n\n[half]\n', r'\![open,teachbox]\e', r'\![raise,OnUserEvent,"0,100"]\e', r'\![raise,"On"User"Event",%username,,"",a"","""","foo,bar"]\e', r'\_a[http://www.asahi.com/]Asahi.com\_a\_s\_a[test]foo\_a\e', r'\_a[test]%j[http://www.asahi.com]%hour時%minute分%second秒\_a', r'\![raise,OnWavePlay,voice\hello.mp3]\e', r'\q[Asahi.com,新聞を読む]', r'\j[\s4]\e', r'\p[2]\s[100]3人目\p3\s[0]4人目', r'\_s[0,2]keroは\_s仲間はずれ\_sです。\e', # illegal cases (to be passed) r'20%終了 (%hour時%minute分%second秒)', r'\g', # illegal cases r'\j[http://www.asahi', r'\s\e', r'\j4\e', r'\q0[#temp0]\e', r'\q[test]\e', r'\q[foo,bar,test]\e', r'\q[起動時間,%exh時間]\e', r'\q[,]\e', r'\URL[しんぶーん][http://www.asahi.com/]\e', r'\_atest\_a', r'\_a[test]', r'\s[normal]', r'\s[0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001]', ] def test_tokenizer(): parser = Parser() for test in testcases: try: print parser.tokenize(unicode(test, 'utf-8')) except ParserError as e: print e def test_parser(error='strict'): parser = Parser(error) for test in testcases: print '*' * 60 print test script = [] test = unicode(test, 'utf-8') while 1: try: script.extend(parser.parse(test)) except ParserError as e: print '-' * 60 print e done, test = e script.extend(done) else: break print '-' * 60 print_script_tree(script) def print_script_tree(tree): for node in tree: if node[0] == SCRIPT_TAG: name, args = node[1], node[2:] print 'TAG', name for n in range(len(args)): if isinstance(args[n], basestring): print '\tARG#{0:d}\t{1}'.format(n + 1, args[n].encode('utf-8')) else: print '\tARG#{0:d}\tTEXT'.format(n + 1) print_text(args[n], 2) elif node[0] == SCRIPT_TEXT: print 'TEXT' print_text(node[1], 1) def print_text(text, indent): for chunk in text: if chunk[0] == TEXT_STRING: print ''.join(('\t' * indent, 'STRING\t"{0}"'.format(chunk[1].encode('utf-8')))) elif chunk[0] == TEXT_META: name, args = chunk[1], chunk[2:] print ''.join(('\t' * indent, 'META\t', name)) for n in range(len(args)): print ''.join(('\t' * indent, '\tARG#{0:d}\t{1}'.format(n + 1, args[n].encode('utf-8')))) if __name__ == '__main__': import os if len(sys.argv) == 2 and sys.argv[1] == 'tokenizer': test_tokenizer() elif len(sys.argv) == 3 and sys.argv[1] == 'parser': test_parser(sys.argv[2]) else: print 'Usage:', os.path.basename(sys.argv[0]), \ '[tokenizer|parser [strict|loose]]' # Syntax of the Sakura Script: # "\e" # "\h" # "\u" # "\s" OpenedSbra Number ClosedSbra # "\b" OpenedSbra Number ClosedSbra # "\n" (OpenedSbra Text ClosedSbra)? # "\w" Number # "\_w" OpenedSbra Number ClosedSbra # "\j" OpenedSbra ID ClosedSbra # "\c" # "\x" # "\t" # "\_q" # "\_s" # "\_n" # "\q" Number OpenedSbra Text ClosedSbra OpenedSbra Text ClosedSbra # "\q" OpenedSbra Text "," ID ClosedSbra # "\z" # "\y" # "\*" # "\v" # "\8" OpenedSbra ID ClosedSbra # "\m" OpenedSbra ID ClosedSbra # "\i" OpenedSbra ID ClosedSbra # "\_e" # "\a" # "\!" OpenedSbra Text ClosedSbra # "\_c" OpenedSbra Text ClosedSbra # "\__c" # "\URL" OpenedSbra Text ClosedSbra [ OpenedSbra Text ClosedSbra OpenedSbra Text ClosedSbra ]* # "\&" OpenedSbra ID ClosedSbra # "\_u" OpenedSbra ID ClosedSbra # "\_m" OpenedSbra ID ClosedSbra # "\_a" OpenedSbra ID ClosedSbra Text "\_a" ninix-aya-4.3.9/lib/ninix/seriko.py000066400000000000000000000604161172114553600172010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import logging import re import sys import random import glib class Controler(object): DEFAULT_FPS = 30.0 # current default def __init__(self, seriko): self.seriko = seriko self.request_parent = lambda *a: None # dummy self.exclusive_actor = None self.base_id = None self.timeout_id = None self.reset_overlays() self.queue = [] self.fps = self.DEFAULT_FPS self.next_tick = 0 self.prev_tick = 0 # XXX self.active = [] self.__move = None self.__dirty = 1 def set_responsible(self, request_method): self.request_parent = request_method def set_base_id(self, window, surface_id): if surface_id == '-2': self.terminate(window) self.base_id = window.surface_id elif surface_id == '-1': self.base_id = window.surface_id else: self.base_id = surface_id self.__dirty = 1 def move_surface(self, xoffset, yoffset): self.__move = (xoffset, yoffset) def append_actor(self, frame, actor): self.active.append((frame, actor)) def update_frame(self, window): frame, actor = self.get_actor_next(window) last_actor = actor while actor is not None: actor.update(window, frame) last_actor = actor frame, actor = self.get_actor_next(window) if last_actor is not None and last_actor.exclusive and \ last_actor.terminate_flag and self.exclusive_actor is None: # XXX self.invoke_restart(window) def get_actor_next(self, window): if self.active: self.active.sort() if self.active[0][0] <= self.next_tick: return self.active.pop(0) return None, None def update(self, window): ## FIXME: Use g_source_get_time. (glib.glib_version > (2, 27, 3)) current_tick = glib.get_current_time() # [sec] quality = self.request_parent( 'GET', 'get_preference', 'animation_quality') self.fps = self.DEFAULT_FPS * quality if self.prev_tick == 0: ## First time delta_tick = 1000.0 / self.fps # [msec] else: delta_tick = (current_tick - self.prev_tick) * 1000.0 # [msec] self.next_tick += delta_tick self.prev_tick = current_tick self.update_frame(window) if self.__dirty: window.update_frame_buffer() self.__dirty = 0 if self.__move is not None: window.move_surface(*self.__move) self.__move = None self.timeout_id = glib.timeout_add(int(1000.0 / self.fps), # [msec] self.update, window) return False def lock_exclusive(self, window, actor): assert self.exclusive_actor is None self.terminate(window) self.exclusive_actor = actor actor.set_post_proc(self.unlock_exclusive, (window, actor)) self.__dirty = 1 # XXX def unlock_exclusive(self, window, actor): assert self.exclusive_actor == actor self.exclusive_actor = None def reset_overlays(self): self.overlays = {} self.__dirty = 1 def remove_overlay(self, actor): try: del self.overlays[actor] except KeyError: pass self.__dirty = 1 def add_overlay(self, window, actor, pixbuf_id, x, y): if pixbuf_id == '-2': self.terminate(window) if pixbuf_id in ['-1', '-2']: self.remove_overlay(actor) return self.overlays[actor] = (pixbuf_id, x, y) self.__dirty = 1 def invoke_actor(self, window, actor): if self.exclusive_actor is not None: interval = actor.get_interval() if interval.startswith('talk') or interval == 'yen-e': return self.queue.append(actor) return if actor.exclusive: self.lock_exclusive(window, actor) actor.invoke(window, self.next_tick) def invoke(self, window, actor_id, update=0): if self.base_id not in self.seriko: return for actor in self.seriko[self.base_id]: if actor_id == actor.get_id(): self.invoke_actor(window, actor) break def invoke_yen_e(self, window, surface_id): if surface_id not in self.seriko: return for actor in self.seriko[surface_id]: if actor.get_interval() == 'yen-e': self.invoke_actor(window, actor) break def invoke_talk(self, window, surface_id, count): if surface_id not in self.seriko: return 0 interval_count = None for actor in self.seriko[surface_id]: interval = actor.get_interval() if interval.startswith('talk'): interval_count = int(interval[5]) # XXX break if interval_count is not None and count >= interval_count: self.invoke_actor(window, actor) return 1 else: return 0 def invoke_runonce(self, window): if self.base_id not in self.seriko: return for actor in self.seriko[self.base_id]: if actor.get_interval() == 'runonce': self.invoke_actor(window, actor) def invoke_always(self, window): if self.base_id not in self.seriko: return for actor in self.seriko[self.base_id]: interval = actor.get_interval() if interval in ['always', 'sometimes', 'rarely'] or \ interval.startswith('random'): self.invoke_actor(window, actor) def invoke_restart(self, window): if self.base_id not in self.seriko: return for actor in self.seriko[self.base_id]: if actor in self.queue: self.queue.remove(actor) self.invoke_actor(window, actor) def invoke_kinoko(self, window): # XXX if self.base_id not in self.seriko: return for actor in self.seriko[self.base_id]: if actor.get_interval() in ['always', 'runonce', 'sometimes', 'rarely',]: self.invoke_actor(window, actor) def reset(self, window, surface_id): self.queue = [] self.terminate(window) self.next_tick = 0 self.prev_tick = 0 # XXX self.set_base_id(window, window.surface_id) self.base_id = surface_id if surface_id in self.seriko else \ window.surface_id self.__dirty = 1 # XXX def start(self, window): self.invoke_runonce(window) self.invoke_always(window) if self.timeout_id is not None: glib.source_remove(self.timeout_id) self.timeout_id = glib.timeout_add( int(1000.0 / self.fps), # [msec] self.update, window) def terminate(self, window): if self.base_id in self.seriko: for actor in self.seriko[self.base_id]: actor.terminate() self.reset_overlays() self.active = [] self.__move = None self.__dirty = 1 def destroy(self): if self.timeout_id is not None: glib.source_remove(self.timeout_id) self.timeout_id = None def iter_overlays(self): actors = list(self.overlays.keys()) actors[:] = [(actor.get_id(), actor) for actor in actors] actors.sort() actors[:] = [actor for actor_id, actor in actors] for actor in actors: pixbuf_id, x, y = self.overlays[actor] ##logging.debug( ## 'actor={0:d}, id={1}, x={2:d}, y={3:d}'.format( ## actor.get_id(), pixbuf_id, x, y)) yield pixbuf_id, x, y class Actor(object): def __init__(self, actor_id, interval): self.id = actor_id self.interval = interval self.patterns = [] self.last_method = None self.exclusive = 0 self.post_proc = None self.terminate_flag = 1 def set_post_proc(self, proc, args): assert self.post_proc is None self.post_proc = (proc, args) def set_exclusive(self): self.exclusive = 1 def get_id(self): return self.id def get_interval(self): return self.interval def get_patterns(self): return self.patterns def add_pattern(self, surface, interval, method, args): self.patterns.append((surface, interval, method, args)) def invoke(self, window, base_frame): self.terminate_flag = 0 def update(self, window, base_frame): if self.terminate_flag: return False pass def terminate(self): self.terminate_flag = 1 if self.post_proc is not None: proc, args = self.post_proc self.post_proc = None proc(*args) def get_surface_ids(self): surface_ids = [] for surface, interval, method, args in self.patterns: if method == 'base': surface_ids.append(surface) return surface_ids def show_pattern(self, window, surface, method, args): if self.last_method in ['overlay', 'overlayfast']: window.remove_overlay(self) if method == 'move': window.seriko.move_surface(args[0], args[1]) ## FIXME elif method in ['overlay', 'overlayfast']: window.add_overlay(self, surface, args[0], args[1]) elif method == 'base': window.seriko.set_base_id(window, surface) ## FIXME elif method == 'start': window.invoke(args[0], update=1) elif method == 'alternativestart': window.invoke(random.choice(args), update=1) else: raise RuntimeError, 'should not reach here' self.last_method = method class ActiveActor(Actor): # always def __init__(self, actor_id, interval): Actor.__init__(self, actor_id, interval) self.wait = 0 self.pattern = 0 def invoke(self, window, base_frame): self.terminate() self.terminate_flag = 0 self.pattern = 0 self.update(window, base_frame) def update(self, window, base_frame): if self.terminate_flag: return False if self.pattern == 0: self.surface_id = window.get_surface() surface, interval, method, args = self.patterns[self.pattern] self.pattern += 1 if self.pattern == len(self.patterns): self.pattern = 0 self.show_pattern(window, surface, method, args) window.append_actor(base_frame + interval, self) return False class RandomActor(Actor): # sometimes, rarely, randome def __init__(self, actor_id, interval, wait_min, wait_max): Actor.__init__(self, actor_id, interval) self.wait_min = wait_min self.wait_max = wait_max self.reset() def reset(self): self.wait = random.randint(self.wait_min, self.wait_max) self.pattern = 0 def invoke(self, window, base_frame): self.terminate() self.terminate_flag = 0 self.reset() window.append_actor(base_frame + self.wait, self) def update(self, window, base_frame): if self.terminate_flag: return False if self.pattern == 0: self.surface_id = window.get_surface() surface, interval, method, args = self.patterns[self.pattern] self.pattern += 1 if self.pattern < len(self.patterns): self.wait = interval else: self.reset() self.show_pattern(window, surface, method, args) window.append_actor(base_frame + self.wait, self) return False class OneTimeActor(Actor): # runone def __init__(self, actor_id, interval): Actor.__init__(self, actor_id, interval) self.wait = -1 self.pattern = 0 def invoke(self, window, base_frame): self.terminate() self.terminate_flag = 0 self.wait = 0 self.pattern = 0 self.update(window, base_frame) def update(self, window, base_frame): if self.terminate_flag: return False if self.pattern == 0: self.surface_id = window.get_surface() surface, interval, method, args = self.patterns[self.pattern] self.pattern += 1 if self.pattern < len(self.patterns): self.wait = interval else: self.wait = -1 # done self.terminate() self.show_pattern(window, surface, method, args) if self.wait >= 0: window.append_actor(base_frame + self.wait, self) return False class PassiveActor(Actor): # never, yen-e, talk def __init__(self, actor_id, interval): Actor.__init__(self, actor_id, interval) self.wait = -1 def invoke(self, window, base_frame): self.terminate() self.terminate_flag = 0 self.wait = 0 self.pattern = 0 self.update(window, base_frame) def update(self, window, base_frame): if self.terminate_flag: return False if self.pattern == 0: self.surface_id = window.get_surface() surface, interval, method, args = self.patterns[self.pattern] self.pattern += 1 if self.pattern < len(self.patterns): self.wait = interval else: self.wait = -1 # done self.terminate() self.show_pattern(window, surface, method, args) if self.wait >= 0: window.append_actor(base_frame + self.wait, self) return False class Mayuna(Actor): def set_exclusive(self): pass def show_pattern(self, window, surface, method, args): pass re_seriko_interval = re.compile('^([0-9]+)interval$') re_seriko_interval_value = re.compile('^(sometimes|rarely|random,[0-9]+|always|runonce|yesn-e|talk,[0-9]+|never)$') re_seriko_pattern = re.compile(r'^([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]+)*\])?$') re_seriko2_interval = re.compile('^animation([0-9]+)\.interval$') re_seriko2_interval_value = re.compile('^(sometimes|rarely|random,[0-9]+|always|runonce|yesn-e|talk,[0-9]+|never)$') re_seriko2_pattern = re.compile(r'^(overlay|overlayfast|base|move|start|alternativestart|)\s*,\s*([0-9]+|-[12])\s*,\s*([+-]?[0-9]+)\s*,?\s*([+-]?[0-9]+)?\s*,?\s*([+-]?[0-9]+)?\s*,?\s*(\([0-9]+(\.[0-9]+)*\))?$') def get_actors(config): version = None buf = [] for key, value in config.items(): if version == 1: match = re_seriko_interval.match(key) elif version == 2: match = re_seriko2_interval.match(key) else: match1 = re_seriko_interval.match(key) match2 = re_seriko2_interval.match(key) if match1: version = 1 match = match1 elif match2: version = 2 match = match2 else: continue if not match: continue if version == 1 and not re_seriko_interval_value.match(value): continue if version == 2 and not re_seriko2_interval_value.match(value): continue buf.append((int(match.group(1)), value)) actors = [] for actor_id, interval in buf: if interval == 'always': actor = ActiveActor(actor_id, interval) elif interval == 'sometimes': actor = RandomActor(actor_id, interval, 0, 10000) # 0 to 10 seconds elif interval == 'rarely': actor = RandomActor(actor_id, interval, 20000, 60000) elif interval.startswith('random'): actor = RandomActor(actor_id, interval, 0, 1000 * int(interval[7])) elif interval == 'runonce': actor = OneTimeActor(actor_id, interval) elif interval == 'yen-e': actor = PassiveActor(actor_id, interval) elif interval.startswith('talk'): actor = PassiveActor(actor_id, interval) elif interval == 'never': actor = PassiveActor(actor_id, interval) if version == 1: key = ''.join((str(actor_id), 'option')) else: key = ''.join(('animation', str(actor_id), '.option')) if key in config and config[key] == 'exclusive': actor.set_exclusive() try: for n in range(128): # up to 128 patterns (0 - 127) if version == 1: key = ''.join((str(actor_id), 'pattern', str(n))) else: key = ''.join(('animation', str(actor_id), '.pattern', str(n))) if key not in config: key = ''.join((str(actor_id), 'patturn', str(n))) # only for version 1 if key not in config: continue # XXX pattern = config[key] if version == 1: match = re_seriko_pattern.match(pattern) else: match = re_seriko2_pattern.match(pattern) if not match: raise ValueError, 'unsupported pattern: {0}'.format(pattern) if version == 1: surface = str(int(match.group(1))) interval = abs(int(match.group(2))) * 10 method = match.group(3) else: method = match.group(1) surface = str(int(match.group(2))) interval = abs(int(match.group(3))) if method == '': method = 'base' if method == 'start': group = match.group(4) if group is None: raise ValueError, 'syntax error: {0}'.format(pattern) args = [int(group)] elif method == 'alternativestart': args = match.group(6) if args is None: raise ValueError, 'syntax error: {0}'.format(pattern) args = [int(s) for s in args[1:-1].split('.')] else: if surface in ['-1', '-2']: x = 0 y = 0 else: x = int(match.group(4) or 0) y = int(match.group(5) or 0) args = [x, y] actor.add_pattern(surface, interval, method, args) except ValueError as error: logging.error(''.join(('seriko.py: ', str(error)))) continue if not actor.get_patterns(): logging.error( 'seriko.py: animation group #{0:d} has no pattern (ignored)'.format(actor_id)) continue actors.append(actor) actors[:] = [(actor.get_id(), actor) for actor in actors] actors.sort() actors[:] = [actor for actor_id, actor in actors] return actors re_mayuna_interval = re.compile('^([0-9]+)interval$') re_mayuna_interval_value = re.compile('^(bind)$') re_mayuna_pattern = re.compile(r'^([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]+)*\])?$') re_mayuna2_interval = re.compile('^animation([0-9]+)\.interval$') re_mayuna2_interval_value = re.compile('^(bind)$') re_mayuna2_pattern = re.compile(r'^(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]+)*\))?$') def get_mayuna(config): version = None buf = [] for key, value in config.items(): if version == 1: match = re_mayuna_interval.match(key) elif version == 2: match = re_mayuna2_interval.match(key) else: match1 = re_mayuna_interval.match(key) match2 = re_mayuna2_interval.match(key) if match1: version = 1 match = match1 elif match2: version = 2 match = match2 else: continue if not match: continue if version == 1 and not re_mayuna_interval_value.match(value): continue if version == 2 and not re_mayuna2_interval_value.match(value): continue buf.append((int(match.group(1)), value)) mayuna = [] for mayuna_id, interval in buf: ##assert interval == 'bind' actor = Mayuna(mayuna_id, interval) try: for n in range(128): # up to 128 patterns (0 - 127) if version == 1: key = ''.join((str(mayuna_id), 'pattern', str(n))) else: key = ''.join(('animation', str(mayuna_id), '.pattern', str(n))) if key not in config: key = ''.join((str(mayuna_id), 'patturn', str(n))) # only for version 1 if key not in config: continue # XXX pattern = config[key] if version == 1: match = re_mayuna_pattern.match(pattern) else: match = re_mayuna2_pattern.match(pattern) if not match: raise ValueError, 'unsupported pattern: {0}'.format(pattern) if version == 1: surface = str(int(match.group(1))) interval = abs(int(match.group(2))) * 10 method = match.group(3) else: method = match.group(1) surface = str(int(match.group(2))) interval = abs(int(match.group(3))) if method not in ['bind', 'add', 'reduce', 'insert']: continue else: if surface in ['-1', '-2']: x = 0 y = 0 else: x = int(match.group(4) or 0) y = int(match.group(5) or 0) args = [x, y] actor.add_pattern(surface, interval, method, args) except ValueError as error: logging.error(''.join(('seriko.py: ', str(error)))) continue if not actor.get_patterns(): logging.error( 'seriko.py: animation group #{0:d} has no pattern (ignored)'.format(mayuna_id)) continue mayuna.append(actor) mayuna[:] = [(actor.get_id(), actor) for actor in mayuna] mayuna.sort() mayuna[:] = [actor for actor_id, actor in mayuna] return mayuna # find ~/.ninix -name 'surface*a.txt' | xargs python seriko.py def test(): import config # XXX: ninix.config if len(sys.argv) == 1: print 'Usage:', sys.argv[0], '[surface??a.txt ...]' for filename in sys.argv[1:]: print 'Reading', filename, '...' for actor in get_actors(config.create_from_file(filename)): print '#{0:d}'.format(actor.get_id()), print actor.__class__.__name__, print '({0})'.format(actor.get_interval()) print 'number of patterns =', len(actor.get_patterns()) for pattern in actor.get_patterns(): print 'surface={0}, interval={1:d}, method={2}, args={3}'.format(*pattern) for actor in get_mayuna(config.create_from_file(filename)): print '#{0:d}'.format(actor.get_id()), print actor.__class__.__name__, print '({0})'.format(actor.get_interval()) print 'number of patterns =', len(actor.get_patterns()) for pattern in actor.get_patterns(): print 'surface={0}, interval={1:d}, method={2}, args={3}'.format(*pattern) if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/sstp.py000066400000000000000000000362061172114553600166760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import codecs import logging import socket from collections import OrderedDict import ninix.entry_db import ninix.script import ninix.version from ninix.sstplib import AsynchronousSSTPServer, BaseSSTPRequestHandler class SSTPServer(AsynchronousSSTPServer): def __init__(self, address): self.request_parent = lambda *a: None # dummy AsynchronousSSTPServer.__init__(self, address, SSTPRequestHandler) self.request_handler = None def shutdown_request(self, request): if self.request_handler is not None: # XXX: send_* methods can be called from outside of the handler pass # keep alive else: AsynchronousSSTPServer.shutdown_request(self, request) def set_responsible(self, request_method): self.request_parent = request_method def send_response(self, code, data=None): try: self.request_handler.send_response(code) if data is not None: self.request_handler.wfile.write(data) self.request_handler.force_finish() self.socket.shutdown(socket.SHUT_WR) # XXX except IOError: pass self.request_handler = None def send_answer(self, value): charset = self.request_handler.headers.get('charset', 'Shift_JIS') answer = ''.join((value.encode(charset, 'ignore'), '\r\n\r\n')) self.send_response(200, answer) # OK def send_no_content(self): self.send_response(204) # No Content def send_sstp_break(self): self.send_response(210) # Break def send_timeout(self): self.send_response(408) # Request Timeout def close(self): self.socket.close() class SSTPRequestHandler(BaseSSTPRequestHandler): def handle(self): if not self.server.request_parent('GET', 'get_sakura_cantalk'): self.error = self.version = None if not self.parse_request(self.rfile.readline()): return self.send_error(512) else: BaseSSTPRequestHandler.handle(self) def force_finish(self): BaseSSTPRequestHandler.finish(self) def finish(self): if self.server.request_handler is None: BaseSSTPRequestHandler.finish(self) # SEND def do_SEND_1_0(self): self.handle_send(1.0) def do_SEND_1_1(self): self.handle_send(1.1) def do_SEND_1_2(self): self.handle_send(1.2) def do_SEND_1_3(self): self.handle_send(1.3) def do_SEND_1_4(self): self.handle_send(1.4) def handle_send(self, version): if not self.check_decoder(): return sender = self.get_sender() if sender is None: return if version == 1.3: handle = self.get_handle() if handle is None: return else: handle = None script_odict = self.get_script_odict() if script_odict is None: return if version in [1.0, 1.1]: entry_db = None elif version in [1.2, 1.3, 1.4]: entry_db = self.get_entry_db() if entry_db is None: return self.enqueue_request(sender, None, handle, script_odict, entry_db) # NOTIFY def do_NOTIFY_1_0(self): self.handle_notify(1.0) def do_NOTIFY_1_1(self): self.handle_notify(1.1) def handle_notify(self, version): if not self.check_decoder(): return sender = self.get_sender() if sender is None: return event = self.get_event() if event is None: return if version == 1.0: entry_db = None elif version == 1.1: script_odict = self.get_script_odict() if script_odict is None: return entry_db = self.get_entry_db() if entry_db is None: return self.enqueue_request(sender, event, None, script_odict, entry_db) def enqueue_request(self, sender, event, handle, script_odict, entry_db): try: address = self.client_address[0] except: address = self.client_address if entry_db is None or entry_db.is_empty(): self.send_response(200) # OK show_sstp_marker, use_translator = self.get_options() self.server.request_parent( 'NOTIFY', 'enqueue_request', event, script_odict, sender, handle, address, show_sstp_marker, use_translator, entry_db, None) elif self.server.request_handler: self.send_response(409) # Conflict else: show_sstp_marker, use_translator = self.get_options() self.server.request_parent( 'NOTIFY', 'enqueue_request', event, script_odict, sender, handle, address, show_sstp_marker, use_translator, entry_db, self.server) self.server.request_handler = self # keep alive PROHIBITED_TAGS = [r'\j', r'\-', r'\+', r'\_+', r'\!'] def check_script(self, script): if not self.local_request(): parser = ninix.script.Parser() nodes = [] while 1: try: nodes.extend(parser.parse(script)) except ninix.script.ParserError as e: done, script = e nodes.extend(done) else: break for node in nodes: if node[0] == ninix.script.SCRIPT_TAG and \ node[1] in self.PROHIBITED_TAGS: self.send_response(400) # Bad Request self.log_error('Script: tag {0} not allowed'.format(node[1])) return 1 return 0 def get_script(self): charset = self.headers.get('charset', 'Shift_JIS') script = self.headers.get('script', None) if script is None: self.send_response(400) # Bad Request self.log_error('Script: header field not found') return None return unicode(script, charset, 'replace') def get_script_odict(self): charset = self.headers.get('charset', 'Shift_JIS') script_odict = OrderedDict() if_ghost = None for header in self.headers.headers: line = unicode(header, charset, 'replace') if not line.lower().startswith('script:'): if line.lower().startswith('ifghost:'): if_ghost = line[8:].strip() else: if_ghost = None continue script = line[7:].strip() if self.check_script(script): return if if_ghost is None: script_odict[''] = script else: script_odict[if_ghost] = script if_ghost = None return script_odict def get_script_if_ghost(self, current=0): charset = self.headers.get('charset', 'Shift_JIS') default = None i, j = 0, len(self.headers.headers) while i < j: line = unicode(self.headers.headers[i], charset, 'replace') i += 1 if line.lower().startswith('ifghost:') and i < j: if_ghost = line[8:].strip() line = unicode(self.headers.headers[i], charset, 'replace') i += 1 if not line.lower().startswith('script:'): continue script = line[7:].strip() if current: # NOTIFY ghost = self.server.request_parent('GET', 'get_ghost_name') if ghost == if_ghost: return script, if_ghost else: # SEND if self.server.request_parent('GET', 'if_ghost', if_ghost): return script, if_ghost if default is None: default = script, if_ghost if default is None: script = self.headers.get('script', None) if script is not None: script = unicode(script, charset, 'replace') default = script, None return default def get_entry_db(self): charset = self.headers.get('charset', 'Shift_JIS') entry_db = ninix.entry_db.EntryDatabase() for line in self.headers.getallmatchingheaders('entry'): value = unicode(line[6:], charset, 'replace') entry = value.split(',', 1) if len(entry) != 2: self.send_response(400) # Bad Request return None entry_db.add(entry[0].strip(), entry[1].strip()) return entry_db def get_event(self): charset = self.headers.get('charset', 'Shift_JIS') event = self.headers.get('event', None) if event is None: self.send_response(400) # Bad Request self.log_error('Event: header field not found') return None buf = [unicode(event, charset, 'replace')] for i in range(8): value = self.headers.get(''.join(('reference', str(i))), None) if value is not None: value = unicode(value, charset, 'replace') buf.append(value) return tuple(buf) def get_sender(self): charset = self.headers.get('charset', 'Shift_JIS') sender = self.headers.get('sender', None) if sender is None: self.send_response(400) # Bad Request self.log_error('Sender: header field not found') return None return unicode(sender, charset, 'replace') def get_handle(self): path = self.headers.get('hwnd', None) if path is None: self.send_response(400) # Bad Request self.log_error('HWnd: header field not found') return None handle = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: handle.connect(path) except socket.error: handle = None # discard socket object logging.error('cannot open Unix socket: {0}'.format(path)) if handle is None: self.send_response(400) # Bad Request self.log_error('Invalid HWnd: header field') return None return handle def check_decoder(self): charset = self.headers.get('charset', 'Shift_JIS') try: codecs.lookup(charset) except: self.send_response(420, 'Refuse (unsupported charset)') self.log_error('Unsupported charset {0}'.format(repr(charset))) else: return 1 return 0 def get_options(self): show_sstp_marker = use_translator = 1 for option in self.headers.get('option', '').split(','): option = option.strip() if option == 'nodescript' and self.local_request(): show_sstp_marker = 0 elif option == 'notranslate': use_translator = 0 return show_sstp_marker, use_translator def local_request(self): result = 0 try: path = self.client_address result = 1 except: host, port = self.client_address result = host == '127.0.0.1' return result # EXECUTE def do_EXECUTE_1_0(self): self.handle_command() def do_EXECUTE_1_2(self): self.handle_command() def do_EXECUTE_1_3(self): if not self.local_request(): host, port = self.client_address self.send_response(420) self.log_error( 'Unauthorized EXECUTE/1.3 request from {0}'.format(host)) return self.handle_command() def handle_command(self): if not self.check_decoder(): return sender = self.get_sender() if sender is None: return command = self.get_command() charset = self.headers.get('charset', 'Shift_JIS') if command is None: return elif command == 'getname': self.send_response(200) name = self.server.request_parent('GET', 'get_ghost_name') self.wfile.write(''.join((name.encode(charset, 'ignore'), '\r\n'))) self.wfile.write('\r\n') elif command == 'getversion': self.send_response(200) self.wfile.write('ninix-aya {0}\r\n'.format(ninix.version.VERSION)) self.wfile.write('\r\n') elif command == 'quiet': self.send_response(200) self.server.request_parent('NOTIFY', 'keep_silence', True) elif command == 'restore': self.send_response(200) self.server.request_parent('NOTIFY', 'keep_silence', False) elif command == 'getnames': self.send_response(200) for name in self.server.request_parent('GET', 'get_ghost_names'): self.wfile.write( ''.join((name.encode(charset, 'ignore'), '\r\n'))) self.wfile.write('\r\n') elif command == 'checkqueue': self.send_response(200) count, total = self.server.request_parent( 'GET', 'check_request_queue', sender) self.wfile.write(''.join((count, '\r\n'))) self.wfile.write(''.join((total, '\r\n'))) self.wfile.write('\r\n') else: self.send_response(501) # Not Implemented self.log_error('Not Implemented ({0})'.format(command)) def get_command(self): charset = self.headers.get('charset', 'Shift_JIS') command = self.headers.get('command', None) if command is None: self.send_response(400) # Bad Request self.log_error('Command: header field not found') return None return unicode(command, charset, 'replace').lower() def do_COMMUNICATE_1_1(self): if not self.check_decoder(): return sender = self.get_sender() if sender is None: return sentence = self.get_sentence() if sentence is None: return self.send_response(200) # OK self.server.request_parent( 'NOTIFY', 'enqueue_event', 'OnCommunicate', sender, sentence) return def get_sentence(self): charset = self.headers.get('charset', 'Shift_JIS') sentence = self.headers.get('sentence', None) if sentence is None: self.send_response(400) # Bad Request self.log_error('Sentence: header field not found') return None return unicode(sentence, charset, 'replace') ninix-aya-4.3.9/lib/ninix/sstplib.py000066400000000000000000000110301172114553600173510ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # sstplib.py - an SSTP library module in Python # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import mimetools import re import select import socket import SocketServer import logging import time class SSTPServer(SocketServer.TCPServer): allow_reuse_address = True class AsynchronousSSTPServer(SSTPServer): def handle_request(self): r, w, e = select.select([self.socket], [], [], 0) if not r: return SSTPServer.handle_request(self) class BaseSSTPRequestHandler(SocketServer.StreamRequestHandler): 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', } MessageClass = mimetools.Message re_requestsyntax = re.compile('^([A-Z]+) SSTP/([0-9]\\.[0-9])$') def parse_request(self, requestline): if requestline.endswith('\r\n'): requestline = requestline[:-2] elif requestline.endswith('\n'): requestline = requestline[:-1] self.requestline = requestline match = self.re_requestsyntax.match(requestline) if not match: self.requestline = '-' self.send_error(400, 'Bad Request {0}'.format(repr(requestline))) return 0 self.command, self.version = match.groups() self.headers = self.MessageClass(self.rfile, 0) return 1 def handle(self): self.error = self.version = None if not self.parse_request(self.rfile.readline()): return name = 'do_{0}_{1}_{2}'.format(self.command, self.version[0], self.version[2]) if not hasattr(self, name): self.send_error( 501, 'Not Implemented ({0}/{1})'.format(self.command, self.version)) return method = getattr(self, name) method() def send_error(self, code, message=None): self.error = code self.log_error(message or self.responses[code]) self.send_response(code, self.responses[code]) def send_response(self, code, message=None): self.log_request(code, message) self.wfile.write('SSTP/{0} {1:d} {2}\r\n\r\n'.format( self.version or '1.0', code, self.responses[code])) def log_error(self, message): logging.error('[{0}] {1}\n'.format(self.timestamp(), message)) def log_request(self, code, message=None): if self.requestline == '-': request = self.requestline else: request = ''.join(('"', self.requestline, '"')) logging.info('{0} [{1}] {2} {3:d} {4}\n'.format( self.client_hostname(), self.timestamp(), request, code, message or self.responses[code])) def client_hostname(self): try: host, port = self.client_address except: return 'localhost' try: hostname, aliaslist, ipaddrlist = socket.gethostbyaddr(host) except socket.error: hostname = host return hostname month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] def timestamp(self): t = time.localtime(time.time()) m = self.month_names[t[1] - 1] return '{0:02d}/{1}/{2:d}:{3:02d}:{4:02d}:{5:02d} {6:+05d}'.format( t[2], m, t[0], t[3], t[4], t[5], -time.timezone / 36) def test(ServerClass = SSTPServer, HandlerClass = BaseSSTPRequestHandler, port = 9801): sstpd = ServerClass(('', port), HandlerClass) print 'Serving SSTP on port {0:d} ...'.format(port) print 'Allow reuse address: {0:d}'.format( sstpd.socket.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)) sstpd.serve_forever() if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/surface.py000066400000000000000000001406211172114553600173320ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import os import re import urllib import urlparse import random import logging import gtk import cairo import ninix.seriko import ninix.pix class Surface(object): # keyval/name mapping from ninix.keymap import keymap_old, keymap_new def __init__(self): self.window = [] self.desc = None self.request_parent = lambda *a: None # dummy self.mikire = 0 self.kasanari = 0 def set_responsible(self, request_method): self.request_parent = request_method def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { 'stick_window': self.window_stick, } handler = handlers.get(event, getattr(self, event, None)) if handler is None: result = self.request_parent( event_type, event, *arglist, **argdict) else: result = handler(*arglist, **argdict) if event_type == 'GET': return result def finalize(self): for surface_window in self.window: surface_window.destroy() self.window = [] def create_gtk_window(self, title, skip_taskbar): window = ninix.pix.TransparentWindow() window.set_focus_on_map(False) window.set_title(title) window.set_decorated(False) window.set_resizable(False) if skip_taskbar: window.set_skip_taskbar_hint(True) window.connect('delete_event', self.delete) window.connect('key_press_event', self.key_press) window.connect('window_state_event', self.window_state) window.set_events(gtk.gdk.KEY_PRESS_MASK) window.realize() return window def identify_window(self, win): for surface_window in self.window: if win == surface_window.window.window: return True return False def window_stayontop(self, flag): for surface_window in self.window: gtk_window = surface_window.window gtk_window.set_keep_above(flag) def window_iconify(self, flag): gtk_window = self.window[0].window iconified = gtk_window.window.get_state() & \ gtk.gdk.WINDOW_STATE_ICONIFIED if flag and not iconified: gtk_window.iconify() elif not flag and iconified: gtk_window.deiconify() def window_state(self, window, event): if not self.request_parent('GET', 'is_running'): return if not (event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED): return if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: if window == self.window[0].window: self.request_parent('NOTIFY', 'notify_iconified') for surface_window in self.window: gtk_window = surface_window.window if gtk_window != window and \ not gtk_window.window.get_state() & \ gtk.gdk.WINDOW_STATE_ICONIFIED: gtk_window.iconify() else: for surface_window in self.window: gtk_window = surface_window.window if gtk_window != window and \ gtk_window.window.get_state() & \ gtk.gdk.WINDOW_STATE_ICONIFIED: gtk_window.deiconify() if window == self.window[0].window: self.request_parent('NOTIFY', 'notify_deiconified') return def delete(self, window, event): return True def key_press(self, window, event): name = self.keymap_old.get(event.keyval, event.string) keycode = self.keymap_new.get(event.keyval, event.string) if event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK): if name == 'f12': logging.info('reset surface position') self.reset_position() if name or keycode: self.request_parent( 'NOTIFY', 'notify_event', 'OnKeyPress', name, keycode) return True def window_stick(self, stick): for window in self.window: if stick: window.window.stick() else: window.window.unstick() re_surface_id = re.compile('^surface([0-9]+)$') def get_seriko(self, surface): seriko = {} for basename, (path, config) in surface.items(): match = self.re_surface_id.match(basename) if not match: continue key = match.group(1) # define animation patterns seriko[key] = ninix.seriko.get_actors(config) return seriko def new(self, desc, alias, surface, name, prefix, tooltips): self.desc = desc self.__tooltips = tooltips self.name = name self.prefix = prefix if alias is None: alias0 = alias1 = None else: alias0 = alias.get('sakura.surface.alias') alias1 = alias.get('kero.surface.alias') # load surface pixbufs = {} elements = {} for basename, (path, config) in surface.items(): if path is None: continue if not os.path.exists(path): name, ext = os.path.splitext(path) dgp_path = ''.join((name, '.dgp')) if not os.path.exists(dgp_path): ddp_path = ''.join((name, '.ddp')) if not os.path.exists(ddp_path): logging.error( '{0}: file not found (ignored)'.format(path)) continue else: path = ddp_path else: path = dgp_path elements[basename] = [path] match = self.re_surface_id.match(basename) if not match: continue key = match.group(1) pixbufs[key] = elements[basename] # compose surface elements composite_pixbuf = {} for basename, (path, config) in surface.items(): match = self.re_surface_id.match(basename) if not match: continue key = match.group(1) if 'element0' in config: logging.debug('surface {0}'.format(key)) composite_pixbuf[key] = self.compose_elements(elements, config) pixbufs.update(composite_pixbuf) # check if necessary surfaces have been loaded for key in ['0', '10']: if key not in pixbufs: raise SystemExit, 'cannot load surface #{0} (abort)\n'.format(key) self.__pixbufs = pixbufs # arrange surface configurations region = {} for basename, (path, config) in surface.items(): match = self.re_surface_id.match(basename) if not match: continue key = match.group(1) # define collision areas buf = [] for n in range(256): # "redo" syntax rect = config.get(''.join(('collision', str(n)))) if rect is None: continue values = rect.split(',') if len(values) != 5: continue try: x1, y1, x2, y2 = [int(value) for value in values[:4]] except ValueError: continue buf.append((values[4].strip(), x1, y1, x2, y2)) for part in ['head', 'face', 'bust']: # "inverse" syntax rect = config.get(''.join(('collision.', part))) if not rect: continue try: x1, y1, x2, y2 = [int(value) for value in rect.split(',')] except ValueError: pass buf.append((part.capitalize(), x1, y1, x2, y2)) region[key] = buf self.__region = region # MAYUNA mayuna = {} for basename, (path, config) in surface.items(): match = self.re_surface_id.match(basename) if not match: continue key = match.group(1) # define animation patterns mayuna[key] = ninix.seriko.get_mayuna(config) bind = {} for side in ['sakura', 'kero']: bind[side] = {} for index in range(128): name = self.desc.get( '{0}.bindgroup{1:d}.name'.format(side, index), None) default = self.desc.get( '{0}.bindgroup{1:d}.default'.format(side, index), 0) if name is not None: bind[side][index] = [name, default] self.mayuna = {} for side in ['sakura', 'kero']: self.mayuna[side] = [] for index in range(128): key = self.desc.get('{0}.menuitem{1:d}'.format(side, index), None) if key == '-': self.mayuna[side].append([key, None, 0]) else: try: key = int(key) except: pass else: if key in bind[side]: name = bind[side][key][0].split(',') self.mayuna[side].append([key, name[1], bind[side][key][1]]) # create surface windows for surface_window in self.window: surface_window.destroy() self.window = [] self.__surface = surface self.add_window(0, '0', alias0, mayuna, bind['sakura']) self.add_window(1, '10', alias1, mayuna, bind['kero']) def get_menu_pixmap(self): top_dir = self.prefix name = self.desc.get('menu.background.bitmap.filename', 'menu_background.png') name = name.replace('\\', '/') path_background = os.path.join(top_dir, name) name = self.desc.get('menu.sidebar.bitmap.filename', 'menu_sidebar.png') name = name.replace('\\', '/') path_sidebar = os.path.join(top_dir, name) name = self.desc.get('menu.foreground.bitmap.filename', 'menu_foreground.png') name = name.replace('\\', '/') path_foreground = os.path.join(top_dir, name) return path_background, path_sidebar, path_foreground def get_menu_fontcolor(self): fontcolor_r = self.desc.get_with_type('menu.background.font.color.r', int, 0) fontcolor_g = self.desc.get_with_type('menu.background.font.color.g', int, 0) fontcolor_b = self.desc.get_with_type('menu.background.font.color.b', int, 0) background = (fontcolor_r, fontcolor_g, fontcolor_b) fontcolor_r = self.desc.get_with_type('menu.foreground.font.color.r', int, 0) fontcolor_g = self.desc.get_with_type('menu.foreground.font.color.g', int, 0) fontcolor_b = self.desc.get_with_type('menu.foreground.font.color.b', int, 0) foreground = (fontcolor_r, fontcolor_g, fontcolor_b) return background, foreground def add_window(self, side, default, alias=None, mayuna={}, bind={}): assert len(self.window) == side if side == 0: name = 'sakura' title = self.request_parent('GET', 'get_selfname') or \ ''.join(('surface.', name)) elif side == 1: name = 'kero' title = self.request_parent('GET', 'get_keroname') or \ ''.join(('surface.', name)) else: name = 'char{0:d}'.format(side) title = ''.join(('surface.', name)) skip_taskbar = bool(side >= 1) gtk_window = self.create_gtk_window(title, skip_taskbar) seriko = self.get_seriko(self.__surface) tooltips = {} if name in self.__tooltips: tooltips = self.__tooltips[name] surface_window = SurfaceWindow( gtk_window, side, self.desc, alias, self.__surface, tooltips, self.__pixbufs, seriko, self.__region, mayuna, bind, default) surface_window.set_responsible(self.handle_request) self.window.append(surface_window) def get_mayuna_menu(self): for side, index in [('sakura', 0), ('kero', 1)]: for menu in self.mayuna[side]: if menu[0] != '-': menu[2] = self.window[index].bind[menu[0]][1] return self.mayuna def compose_elements(self, elements, config): error = None for n in range(256): key = ''.join(('element', str(n))) if key not in config: break spec = [value.strip() for value in config[key].split(',')] try: method, filename, x, y = spec x = int(x) y = int(y) except ValueError: error = 'invalid element spec for {0}: {1}'.format(key, config[key]) break basename, ext = os.path.splitext(filename) ext = ext.lower() if ext not in ['.png', '.dgp', '.ddp']: error = 'unsupported file format for {0}: {1}'.format(key, filename) break basename = basename.lower() if basename not in elements: error = '{0} file not found: {1}'.format(key, filename) break pixbuf = elements[basename][0] if n == 0: # base surface pixbuf_list = [pixbuf] elif method == 'overlay': pixbuf_list.append((pixbuf, x, y)) elif method == 'overlayfast': # XXX pixbuf_list.append((pixbuf, x, y)) elif method == 'base': pixbuf_list.append((pixbuf, x, y)) else: error = 'unknown method for {0}: {1}'.format(key, method) break logging.debug('{0}: {1} {2}, x={3:d}, y={4:d}'.format(key, method, filename, x, y)) if error is not None: logging.error(error) pixbuf_list = [] return pixbuf_list def get_window(self, side): if len(self.window) > side: return self.window[side].window # FIXME else: return None def reset_surface(self): for window in self.window: window.reset_surface() def set_surface_default(self, side): if side is None: for side in range(len(self.window)): self.window[side].set_surface_default() elif 0 <= side < len(self.window): self.window[side].set_surface_default() def set_surface(self, side, surface_id): if len(self.window) > side: self.window[side].set_surface(surface_id) def get_surface(self, side): if len(self.window) > side: return self.window[side].get_surface() else: return 0 def get_surface_size(self, side): if len(self.window) > side: return self.window[side].get_surface_size() else: return 0, 0 def get_surface_offset(self, side): if len(self.window) > side: return self.window[side].get_surface_offset() else: return 0, 0 def get_touched_region(self, side, x, y): if len(self.window) > side: return self.window[side].get_touched_region(x, y) else: return '' def get_center(self, side): if len(self.window) > side: return self.window[side].get_center() else: return None, None def get_kinoko_center(self, side): if len(self.window) > side: return self.window[side].get_kinoko_center() else: return None, None def reset_balloon_position(self): for side in range(len(self.window)): x, y = self.get_position(side) direction = self.window[side].direction ox, oy = self.get_balloon_offset(side) self.request_parent( 'NOTIFY', 'set_balloon_direction', side, direction) if direction == 0: # left base_x = x + ox else: w, h = self.get_surface_size(side) base_x = x + w - ox base_y = y + oy self.request_parent( 'NOTIFY', 'set_balloon_position', side, base_x, base_y) def reset_position(self): left, top, scrn_w, scrn_h = ninix.pix.get_workarea() for side in range(len(self.window)): align = self.get_alignment(side) w, h = self.get_surface_size(side) if side == 0: # sakura x = left + scrn_w - w else: b0w, b0h = self.request_parent( 'GET', 'get_balloon_size', side - 1) b1w, b1h = self.request_parent( 'GET', 'get_balloon_size', side) bpx, bpy = self.request_parent( 'GET', 'get_balloon_windowposition', side) o0x, o0y = self.get_balloon_offset(side - 1) o1x, o1y = self.get_balloon_offset(side) offset = max(0, b1w - (b0w - o0x)) if (s0x + o0x - b0w) - offset - w + o1x < left: x = left else: x = (s0x + o0x - b0w) - offset - w + o1x if align == 1: # top y = top else: y = top + scrn_h - h self.set_position(side, x, y) s0x, s0y, s0w, s0h = x, y, w, h # for next loop def set_position(self, side, x, y): if len(self.window) > side: self.window[side].set_position(x, y) def get_position(self, side): if len(self.window) > side: return self.window[side].get_position() else: return 0, 0 def set_alignment_current(self): for side in range(len(self.window)): self.window[side].set_alignment_current() def set_alignment(self, side, align): if len(self.window) > side: self.window[side].set_alignment(align) def get_alignment(self, side): if len(self.window) > side: return self.window[side].get_alignment() else: return 0 def reset_alignment(self): if self.desc.get('seriko.alignmenttodesktop') == 'free': align = 2 else: align = 0 for side in range(len(self.window)): self.set_alignment(side, align) def is_shown(self, side): if len(self.window) > side: return self.window[side].is_shown() else: return False def show(self, side): if len(self.window) > side: self.window[side].show() def hide_all(self): for side in range(len(self.window)): self.window[side].hide() def hide(self, side): if len(self.window) > side: self.window[side].hide() def raise_all(self): for side in range(len(self.window)): self.window[side].raise_() def raise_(self, side): if len(self.window) > side: self.window[side].raise_() def lower_all(self): for side in range(len(self.window)): self.window[side].lower() def lower(self, side): if len(self.window) > side: self.window[side].lower() def invoke(self, side, actor_id): if len(self.window) > side: self.window[side].invoke(actor_id) def invoke_yen_e(self, side, surface_id): if len(self.window) > side: self.window[side].invoke_yen_e(surface_id) def invoke_talk(self, side, surface_id, count): if len(self.window) > side: return self.window[side].invoke_talk(surface_id, count) else: return 0 def set_icon(self, path): pixbuf = None if path is not None: try: pixbuf = ninix.pix.create_pixbuf_from_file(path, is_pnr=False) except: pixbuf = None for window in self.window: window.window.set_icon(pixbuf) def get_mikire(self): ## FIXME return self.mikire def get_kasanari(self): ## FIXME return self.kasanari def get_name(self): return self.name def get_username(self): return None if self.desc is None else self.desc.get('user.defaultname') def get_selfname(self): return None if self.desc is None else self.desc.get('sakura.name') def get_selfname2(self): return None if self.desc is None else self.desc.get('sakura.name2') def get_keroname(self): return None if self.desc is None else self.desc.get('kero.name') def get_friendname(self): return None if self.desc is None else self.desc.get('sakura.friend.name') def get_balloon_offset(self, side): if len(self.window) > side: return self.window[side].get_balloon_offset() return 0, 0 def toggle_bind(self, args): side, bind_id = args self.window[side].toggle_bind(bind_id) def get_collision_area(self, side, part): if len(self.window) > side: return self.window[side].get_collision_area(part) return None def check_mikire_kasanari(self): if not self.is_shown(0): self.mikire = self.kasanari = 0 return left, top, scrn_w, scrn_h = ninix.pix.get_workarea() x0, y0 = self.get_position(0) s0w, s0h = self.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: self.mikire = 1 else: self.mikire = 0 if not self.is_shown(1): self.kasanari = 0 return x1, y1 = self.get_position(1) s1w, s1h = self.get_surface_size(1) if (x0 < x1 + s1w / 2 < x0 + s0w and y0 < y1 + s1h / 2 < y0 + s0h) or \ (x1 < x0 + s0w / 2 < x1 + s1w and y1 < y0 + s0h / 2 < y1 + s1h): self.kasanari = 1 else: self.kasanari = 0 class SurfaceWindow(object): # DnD data types dnd_targets = [ ('text/plain', 0, 0), ] def __init__(self, window, side, desc, alias, surface, tooltips, pixbuf, seriko, region, mayuna, bind, default_id): self.window = window self.side = side self.request_parent = lambda *a: None # dummy self.desc = desc self.alias = alias self.tooltips = tooltips self.align = 0 self.__current_part = '' ## FIXME if self.alias is not None: default_id = self.alias.get(default_id, [default_id])[0] self.surface = surface self.surface_id = default_id self.pixbuf = pixbuf self.current_surface_pixbuf = None # XXX self.seriko = ninix.seriko.Controler(seriko) self.seriko.set_responsible(self.handle_request) self.region = region self.mayuna = mayuna self.bind = bind self.default_id = default_id self.__shown = False self.window_offset = (0, 0) self.position = (0, 0) self.__direction = 0 self.dragged = False self.x_root = None self.y_root = None self.window.connect('leave_notify_event', self.window_leave_notify) # XXX self.window.connect('enter_notify_event', self.window_enter_notify) # XXX # create drawing area self.darea = gtk.DrawingArea() self.darea.show() self.darea.set_events(gtk.gdk.EXPOSURE_MASK| gtk.gdk.BUTTON_PRESS_MASK| gtk.gdk.BUTTON_RELEASE_MASK| gtk.gdk.POINTER_MOTION_MASK| gtk.gdk.POINTER_MOTION_HINT_MASK| gtk.gdk.SCROLL_MASK) self.darea.connect('expose_event', self.redraw) self.darea.connect('button_press_event', self.button_press) self.darea.connect('button_release_event', self.button_release) self.darea.connect('motion_notify_event', self.motion_notify) self.darea.connect('drag_data_received', self.drag_data_received) self.darea.connect('scroll_event', self.scroll) self.darea.drag_dest_set(gtk.DEST_DEFAULT_ALL, self.dnd_targets, gtk.gdk.ACTION_COPY) self.window.add(self.darea) self.window.realize() self.window.window.set_back_pixmap(None, False) def set_responsible(self, request_method): self.request_parent = request_method def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { } handler = handlers.get(event, getattr(self, event, None)) if handler is None: result = self.request_parent( event_type, event, *arglist, **argdict) else: result = handler(*arglist, **argdict) if event_type == 'GET': return result @property def direction(self): return self.__direction @direction.setter def direction(self, direction): self.__direction = direction # 0: left, 1: right self.request_parent( 'NOTIFY', 'set_balloon_direction', self.side, direction) @property def scale(self): return self.request_parent('GET', 'get_preference', 'surface_scale') def drag_data_received(self, widget, context, x, y, data, info, time): logging.debug('Content-type: {0}'.format(data.type)) logging.debug('Content-length: {0:d}'.format(data.get_length())) if str(data.type) == 'text/plain': filelist = [] for line in data.data.split('\r\n'): scheme, host, path, params, query, fragment = \ urlparse.urlparse(line) pathname = urllib.url2pathname(path) if scheme == 'file' and os.path.exists(pathname): filelist.append(pathname) if filelist: self.request_parent( 'NOTIFY', 'enqueue_event', 'OnFileDrop2', chr(1).join(filelist), self.side) return True def append_actor(self, frame, actor): self.seriko.append_actor(frame, actor) def invoke(self, actor_id, update=0): self.seriko.invoke(self, actor_id, update) def invoke_yen_e(self, surface_id): self.seriko.invoke_yen_e(self, surface_id) def invoke_talk(self, surface_id, count): return self.seriko.invoke_talk(self, surface_id, count) def reset_surface(self): surface_id = self.get_surface() self.set_surface(surface_id) def set_surface_default(self): self.set_surface(self.default_id) def set_surface(self, surface_id): if self.alias is not None and surface_id in self.alias: aliases = self.alias.get(surface_id) if aliases: surface_id = random.choice(aliases) if surface_id == '-2': self.seriko.terminate(self) if surface_id in ['-1', '-2']: pass elif surface_id not in self.pixbuf: self.surface_id = self.default_id else: self.surface_id = surface_id self.seriko.reset(self, surface_id) # define collision areas self.collisions = self.region[self.surface_id] # update window offset x, y = self.position # XXX: without window_offset w, h = self.get_surface_size(self.surface_id) dw, dh = self.get_surface_size(self.default_id) # default surface size xoffset = (dw - w) / 2 if self.get_alignment() == 0: yoffset = dh - h left, top, scrn_w, scrn_h = ninix.pix.get_workarea() y = top + scrn_h - dh elif self.get_alignment() == 1: yoffset = 0 else: yoffset = (dh - h) / 2 self.window_offset = (xoffset, yoffset) # resize window self.darea.set_size_request(w, h) self.window.queue_resize() self.seriko.start(self) # relocate window if not self.dragged: # XXX self.set_position(x, y) if self.side < 2: self.request_parent('NOTIFY', 'notify_observer', 'set surface') def iter_mayuna(self, surface_width, surface_height, mayuna, done): for surface, interval, method, args in mayuna.patterns: if method in ['bind', 'add']: if surface in self.pixbuf: x, y = args pixbuf = self.get_pixbuf(surface) w = pixbuf.get_width() h = pixbuf.get_height() # overlay surface pixbuf if x + w > surface_width: w = surface_width - x if y + h > surface_height: h = surface_height - y if x < 0: dest_x = 0 w += x else: dest_x = x if y < 0: dest_y = 0 h += y else: dest_y = y yield method, pixbuf, dest_x, dest_y, w, h, x, y elif method == 'reduce': if surface in self.pixbuf: dest_x, dest_y = args pixbuf = self.get_pixbuf(surface) w = pixbuf.get_width() h = pixbuf.get_height() x = y = 0 # XXX yield method, pixbuf, dest_x, dest_y, w, h, x, y elif method == 'insert': index = args[0] for actor in self.mayuna[self.surface_id]: actor_id = actor.get_id() if actor_id == index: if actor_id in self.bind and self.bind[actor_id][1] and \ actor_id not in done: done.append(actor_id) for result in self.iter_mayuna(surface_width, surface_height, actor, done): yield result else: break else: raise RuntimeError, 'should not reach here' def create_pixbuf_from_file(self, pixbuf_id): assert pixbuf_id in self.pixbuf use_pna = self.request_parent('GET', 'get_preference', 'use_pna') try: pixbuf = ninix.pix.create_pixbuf_from_file( self.pixbuf[pixbuf_id][0], use_pna=use_pna) except: logging.debug('cannot load surface #{0}'.format(pixbuf_id)) return ninix.pix.create_blank_pixbuf(100, 100) for element, x, y in self.pixbuf[pixbuf_id][1:]: try: overlay = ninix.pix.create_pixbuf_from_file( element, use_pna=use_pna) except: continue w = overlay.get_width() h = overlay.get_height() sw = pixbuf.get_width() sh = pixbuf.get_height() if x + w > sw: w = sw - x if y + h > sh: h = sh - y if x < 0: dest_x = 0 w += x else: dest_x = x if y < 0: dest_y = 0 h += y else: dest_y = y overlay.composite(pixbuf, dest_x, dest_y, w, h, x, y, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) return pixbuf def get_pixbuf(self, pixbuf_id): if pixbuf_id not in self.pixbuf: logging.debug('cannot load pixbuf #{0}'.format(pixbuf_id)) return ninix.pix.create_blank_pixbuf(100, 100) return self.create_pixbuf_from_file(pixbuf_id) def draw_region(self): cr = self.darea.window.cairo_create() cr.save() scale = self.scale for part, x1, y1, x2, y2 in self.collisions: x1 = x1 * scale / 100 x2 = x2 * scale / 100 y1 = y1 * scale / 100 y2 = y2 * scale / 100 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() cr.restore() del cr def create_surface_pixbuf(self, surface_id=None): if surface_id is None: surface_id = self.surface_id if surface_id in self.mayuna and self.mayuna[surface_id]: surface_pixbuf = self.get_pixbuf(surface_id) done = [] for actor in self.mayuna[surface_id]: actor_id = actor.get_id() if actor_id in self.bind and self.bind[actor_id][1] and \ actor_id not in done: done.append(actor_id) #surface_pixbuf = self.compose_surface( # surface_pixbuf, actor, done) surface_width = surface_pixbuf.get_width() surface_height = surface_pixbuf.get_height() for method, pixbuf, dest_x, dest_y, w, h, x, y in self.iter_mayuna(surface_width, surface_height, actor, done): if method in ['bind', 'add']: pixbuf.composite(surface_pixbuf, dest_x, dest_y, w, h, x, y, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) elif method == 'reduce': if pixbuf.get_has_alpha(): dest_w = surface_width dest_h = surface_height surface_array = surface_pixbuf.get_pixels_array() array = pixbuf.get_pixels_array() for i in range(h): for j in range(w): if array[i][j][3] == 0: # alpha x = j + dest_x y = i + dest_y if 0 <= x < dest_w and 0 <= y < dest_h: surface_array[y][x][3] = 0 # alpha else: raise RuntimeError, 'should not reach here' else: surface_pixbuf = self.get_pixbuf(surface_id) return surface_pixbuf def update_frame_buffer(self): if not self.request_parent('GET', 'get_preference', 'seriko_inactive'): surface_pixbuf = self.create_surface_pixbuf(self.seriko.base_id) assert surface_pixbuf is not None # draw overlays for pixbuf_id, x, y in self.seriko.iter_overlays(): try: pixbuf = self.get_pixbuf(pixbuf_id) w = pixbuf.get_width() h = pixbuf.get_height() except: continue # overlay surface pixbuf sw = surface_pixbuf.get_width() sh = surface_pixbuf.get_height() if x + w > sw: w = sw - x if y + h > sh: h = sh - y if x < 0: dest_x = 0 w += x else: dest_x = x if y < 0: dest_y = 0 h += y else: dest_y = y pixbuf.composite(surface_pixbuf, dest_x, dest_y, w, h, x, y, 1.0, 1.0, gtk.gdk.INTERP_BILINEAR, 255) else: surface_pixbuf = self.create_surface_pixbuf() w = surface_pixbuf.get_width() h = surface_pixbuf.get_height() scale = self.scale w = max(8, w * scale / 100) h = max(8, h * scale / 100) surface_pixbuf = surface_pixbuf.scale_simple( w, h, gtk.gdk.INTERP_BILINEAR) mask_pixmap = gtk.gdk.Pixmap(None, w, h, 1) surface_pixbuf.render_threshold_alpha( mask_pixmap, 0, 0, 0, 0, w, h, 1) self.window.mask = mask_pixmap self.current_surface_pixbuf = surface_pixbuf self.darea.queue_draw() def redraw(self, darea, event): if self.current_surface_pixbuf is None: # XXX return cr = darea.window.cairo_create() cr.save() cr.set_operator(cairo.OPERATOR_CLEAR) cr.paint() cr.restore() cr.set_source_pixbuf(self.current_surface_pixbuf, 0, 0) alpha_channel = self.request_parent( 'GET', 'get_preference', 'surface_alpha') cr.paint_with_alpha(alpha_channel) del cr if self.request_parent('GET', 'get_preference', 'check_collision'): self.draw_region() def remove_overlay(self, actor): self.seriko.remove_overlay(actor) def add_overlay(self, actor, pixbuf_id, x, y): self.seriko.add_overlay(self, actor, pixbuf_id, x, y) def move_surface(self, xoffset, yoffset): x, y = self.get_position() self.window.resize_move(x, y, xoffset, yoffset) if self.side < 2: args = (self.side, xoffset, yoffset) self.request_parent( 'NOTIFY', 'notify_observer', 'move surface', args) # animation def get_balloon_offset(self): path, config = self.surface[''.join(('surface', self.surface_id))] side = self.side if side == 0: name = 'sakura' x = config.get_with_type('{0}.balloon.offsetx'.format(name), int) y = config.get_with_type('{0}.balloon.offsety'.format(name), int) elif side == 1: name = 'kero' x = config.get_with_type('{0}.balloon.offsetx'.format(name), int) y = config.get_with_type('{0}.balloon.offsety'.format(name), int) else: name = 'char{0:d}'.format(side) x, y = None, None # XXX if x is None: x = self.desc.get_with_type('{0}.balloon.offsetx'.format(name), int, 0) if y is None: y = self.desc.get_with_type('{0}.balloon.offsety'.format(name), int, 0) scale = self.scale x = x * scale / 100 y = y * scale / 100 return x, y def get_collision_area(self, part): for p, x1, y1, x2, y2 in self.collisions: ## FIXME if p == part: scale = self.scale x1 = x1 * scale / 100 x2 = x2 * scale / 100 y1 = y1 * scale / 100 y2 = y2 * scale / 100 return x1, y1, x2, y2 return None def get_surface(self): return self.surface_id def get_surface_size(self, surface_id=None): if surface_id is None: surface_id = self.surface_id if surface_id not in self.pixbuf: w, h = 100, 100 # XXX else: w, h = ninix.pix.get_png_size(self.pixbuf[surface_id][0]) scale = self.scale w = max(8, int(w * scale / 100)) h = max(8, int(h * scale / 100)) return w, h def get_surface_offset(self): return self.window_offset def get_touched_region(self, x, y): for part, x1, y1, x2, y2 in self.collisions: if x1 <= x <= x2 and y1 <= y <= y2: ##logging.debug('{0} touched'.format(part)) return part return '' def __get_with_scaling(self, name, conv): basename = ''.join(('surface', self.surface_id)) path, config = self.surface[basename] value = config.get_with_type(name, conv) if value is not None: scale = self.scale value = conv(value * scale / 100) return value def get_center(self): centerx = self.__get_with_scaling('point.centerx', int) centery = self.__get_with_scaling('point.centery', int) return centerx, centery def get_kinoko_center(self): centerx = self.__get_with_scaling('point.kinoko.centerx', int) centery = self.__get_with_scaling('point.kinoko.centery', int) return centerx, centery def set_position(self, x, y): self.position = (x, y) new_x, new_y = self.get_position() if self.__shown: self.window.resize_move(new_x, new_y) left, top, scrn_w, scrn_h = ninix.pix.get_workarea() direction = 0 if x > left + scrn_w / 2 else 1 self.direction = direction ox, oy = self.get_balloon_offset() if direction == 0: # left base_x = new_x + ox else: w, h = self.get_surface_size() base_x = new_x + w - ox base_y = new_y + oy self.request_parent( 'NOTIFY', 'set_balloon_position', self.side, base_x, base_y) self.request_parent('NOTIFY', 'notify_observer', 'set position') self.request_parent('NOTIFY', 'check_mikire_kasanari') def get_position(self): ## FIXME: position with offset(property) return list(map(lambda x, y: x + y, self.position, self.window_offset)) def set_alignment_current(self): self.set_alignment(self.get_alignment()) def set_alignment(self, align): if align in [0, 1, 2]: self.align = align if self.dragged: # XXX: position will be reset after button release event return if align == 0: left, top, scrn_w, scrn_h = ninix.pix.get_workarea() sw, sh = self.get_surface_size(self.default_id) sx, sy = self.position # XXX: without window_offset sy = top + scrn_h - sh self.set_position(sx, sy) elif align == 1: left, top, scrn_w, scrn_h = ninix.pix.get_workarea() sx, sy = self.position # XXX: without window_offset sy = top self.set_position(sx, sy) else: # free pass def get_alignment(self): return self.align def destroy(self): self.seriko.destroy() self.window.remove(self.darea) self.darea.destroy() self.window.destroy() def is_shown(self): return self.__shown def show(self): if self.__shown: return self.__shown = True x, y = self.get_position() self.window.resize_move(x, y) # XXX: call before showing the window self.darea.show() self.window.show() self.request_parent('NOTIFY', 'notify_observer', 'show', (self.side)) self.request_parent('NOTIFY', 'notify_observer', 'raise', (self.side)) def hide(self): if self.__shown: self.window.hide() self.__shown = False self.request_parent( 'NOTIFY', 'notify_observer', 'hide', (self.side)) def raise_(self): self.window.window.raise_() self.request_parent('NOTIFY', 'notify_observer', 'raise', (self.side)) def lower(self): self.window.window.lower() self.request_parent('NOTIFY', 'notify_observer', 'lower', (self.side)) def button_press(self, window, event): self.request_parent('NOTIFY', 'reset_idle_time') x = int(event.x) y = int(event.y) scale = self.scale x = int(x * 100 / scale) y = int(y * 100 / scale) self.x_root = event.x_root self.y_root = event.y_root click = 1 if event.type == gtk.gdk.BUTTON_PRESS else 2 # automagical raise self.request_parent('NOTIFY', 'notify_observer', 'raise', (self.side)) self.request_parent('NOTIFY', 'notify_surface_click', event.button, click, self.side, x, y) return True def button_release(self, window, event): if self.dragged: self.dragged = False self.set_alignment_current() self.x_root = None self.y_root = None return True def motion_notify(self, darea, event): if event.is_hint: x, y, state = self.darea.window.get_pointer() else: x, y, state = event.x, event.y, event.state scale = self.scale x = int(x * 100 / scale) y = int(y * 100 / scale) part = self.get_touched_region(x, y) if part != self.__current_part: if part == '': self.window.set_tooltip_text(None) self.darea.window.set_cursor(None) self.request_parent( 'NOTIFY', 'notify_event', 'OnMouseLeave', x, y, '', self.side, self.__current_part) else: if part in self.tooltips: tooltip = self.tooltips[part] self.window.set_tooltip_text(tooltip) else: self.window.set_tooltip_text(None) cursor = gtk.gdk.Cursor(gtk.gdk.HAND1) self.darea.window.set_cursor(cursor) self.request_parent( 'NOTIFY', 'notify_event', 'OnMouseEnter', x, y, '', self.side, part) self.__current_part = part if not self.request_parent('GET', 'busy'): if state & gtk.gdk.BUTTON1_MASK: if self.x_root is not None and \ self.y_root is not None: self.dragged = True x_delta = int(event.x_root - self.x_root) y_delta = int(event.y_root - self.y_root) x, y = self.position # XXX: without window_offset self.set_position(x + x_delta, y + y_delta) self.x_root = event.x_root self.y_root = event.y_root elif state & gtk.gdk.BUTTON2_MASK or \ state & gtk.gdk.BUTTON3_MASK: pass else: self.request_parent('NOTIFY', 'notify_surface_mouse_motion', self.side, x, y, part) return True def scroll(self, darea, event): x = int(event.x) y = int(event.y) scale = self.scale x = int(x * 100 / scale) y = int(y * 100 / scale) if event.direction == gtk.gdk.SCROLL_UP: count = 1 elif event.direction == gtk.gdk.SCROLL_DOWN: count = -1 else: count = 0 if count != 0: part = self.get_touched_region(x, y) self.request_parent('NOTIFY', 'notify_event', 'OnMouseWheel', x, y, count, self.side, part) return True def toggle_bind(self, bind_id): if bind_id in self.bind: current = self.bind[bind_id][1] self.bind[bind_id][1] = not current self.reset_surface() def window_enter_notify(self, window, event): x, y, state = event.x, event.y, event.state scale = self.scale x = int(x * 100 / scale) y = int(y * 100 / scale) self.request_parent('NOTIFY', 'notify_event', 'OnMouseEnterAll', x, y, '', self.side, '') def window_leave_notify(self, window, event): x, y, state = event.x, event.y, event.state scale = self.scale x = int(x * 100 / scale) y = int(y * 100 / scale) if self.__current_part != '': # XXX self.request_parent( 'NOTIFY', 'notify_event', 'OnMouseLeave', x, y, '', self.side, self.__current_part) self.__current_part = '' self.request_parent( 'NOTIFY', 'notify_event', 'OnMouseLeaveAll', x, y, '', self.side, '') return True def test(): pass if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/update.py000066400000000000000000000355731172114553600171750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import urlparse import urllib import StringIO import os import hashlib import time import httplib import logging from ninix.home import get_normalized_path class NetworkUpdate(object): __BACKUP_SUFFIX = '.BACKUP' def __init__(self): self.request_parent = lambda *a: None # dummy self.event_queue = [] self.state = None self.backups = [] self.newfiles = [] self.newdirs = [] def set_responsible(self, request_method): self.request_parent = request_method def is_active(self): return self.state is not None def enqueue_event(self, event, ref0=None, ref1=None, ref2=None, ref3=None, ref4=None, ref5=None, ref6=None, ref7=None): self.event_queue.append( (event, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7)) def get_event(self): return None if not self.event_queue else self.event_queue.pop(0) def has_events(self): return 1 if self.event_queue else 0 def start(self, homeurl, ghostdir, timeout=60): url = urlparse.urlparse(homeurl) if not (url[0] == 'http' and url[3] == url[4] == url[5] == ''): self.enqueue_event('OnUpdateFailure', 'bad home URL') self.state = None return try: self.host, port = url[1].split(':') self.port = int(port) except ValueError: self.host = url[1] self.port = 80 self.path = url[2] self.ghostdir = ghostdir self.timeout = timeout self.state = 0 def interrupt(self): self.event_queue = [] self.request_parent( 'NOTIFY', 'enqueue_event', 'OnUpdateFailure', 'artificial') self.state = None self.stop(revert=1) def stop(self, revert=0): self.buffer = [] if revert: for path in self.backups: if os.path.isfile(path): os.rename(path, path[:-len(self.__BACKUP_SUFFIX)]) for path in self.newfiles: if os.path.isfile(path): os.remove(path) for path in self.newdirs: if os.path.isdir(path): os.rmdir(path) self.backups = [] self.newfiles = [] self.newdirs = [] def clean_up(self): for path in self.backups: if os.path.isfile(path): os.remove(path) self.backups = [] def reset_timeout(self): self.timestamp = time.time() def check_timeout(self): return time.time() - self.timestamp > self.timeout LEN_STATE = 5 LEN_PRE = 5 def run(self): if self.state is None or \ self.request_parent('GET', 'check_event_queue'): return 0 elif self.state == 0: self.start_updates() elif self.state == 1: self.connect() elif self.state == 2: self.wait_response() elif self.state == 3: self.get_content() elif self.state == 4: self.schedule = self.make_schedule() if self.schedule is None: return 0 self.final_state = len(self.schedule) * self.LEN_STATE + self.LEN_PRE elif self.state == self.final_state: self.end_updates() elif (self.state - self.LEN_PRE) % self.LEN_STATE == 0: filename, checksum = self.schedule[0] logging.info('UPDATE: {0} {1}'.format(filename, checksum)) self.download(os.path.join(self.path, urllib.quote(filename)), event=1) elif (self.state - self.LEN_PRE) % self.LEN_STATE == 1: self.connect() elif (self.state - self.LEN_PRE) % self.LEN_STATE == 2: self.wait_response() elif (self.state - self.LEN_PRE) % self.LEN_STATE == 3: self.get_content() elif (self.state - self.LEN_PRE) % self.LEN_STATE == 4: filename, checksum = self.schedule.pop(0) self.update_file(unicode(filename, 'Shift_JIS'), checksum) return 1 def start_updates(self): self.enqueue_event('OnUpdateBegin') self.download(os.path.join(self.path, 'updates2.dau')) def download(self, locator, event=0): self.locator = self.encode(locator) self.http = httplib.HTTPConnection(self.host, self.port) if event: self.enqueue_event('OnUpdate.OnDownloadBegin', os.path.basename(locator), self.file_number, self.num_files) self.state += 1 self.reset_timeout() def encode(self, path): return ''.join([self.encode_special(c) for c in path]) def encode_special(self, c): return c if '\x20' < c < '\x7e' else '%{0:02x}'.format(ord(c)) def connect(self): try: self.http.connect() except: self.enqueue_event('OnUpdateFailure', 'connection failed') self.state = None self.stop(revert=1) return if self.check_timeout(): self.enqueue_event('OnUpdateFailure', 'timeout') self.state = None self.stop(revert=1) return self.http.request('GET', self.locator) self.state += 1 self.reset_timeout() def wait_response(self): try: self.response = self.http.getresponse() except: self.enqueue_event('OnUpdateFailure', 'no HTTP response') self.state = None self.stop(revert=1) return if self.check_timeout(): self.enqueue_event('OnUpdateFailure', 'timeout') self.state = None self.stop(revert=1) return code = self.response.status message = self.response.reason if code == 200: pass elif code == 302 and self.redirect(): return elif self.state == 2: # updates2.dau self.enqueue_event('OnUpdateFailure', str(code)) self.state = None return else: filename, checksum = self.schedule.pop(0) logging.error( 'failed to download {0} ({1:d} {2})'.format( filename, code, message)) self.file_number += 1 self.state += 3 return self.buffer = [] size = self.response.getheader('content-length', None) if size is None: self.size = None else: self.size = int(size) self.state += 1 self.reset_timeout() def redirect(self): location = self.response.getheader('location', None) if location is None: return 0 url = urlparse.urlparse(location) if not (url[0] == 'http' and url[3] == url[4] == url[5] == ''): return 0 logging.info('redirected to {0}'.format(location)) self.http.close() try: self.host, port = url[1].split(':') self.port = int(port) except ValueError: self.host = url[1] self.port = 80 self.path = os.path.dirname(url[2]) self.state -= 2 self.download(url[2]) return 1 def get_content(self): data = self.response.read() if not data: if self.check_timeout(): self.enqueue_event('OnUpdateFailure', 'timeout') self.state = None self.stop(revert=1) return elif data is None: return elif data < 0: self.enqueue_event('OnUpdateFailure', 'data retrieval failed') self.state = None self.stop(revert=1) return if data: self.buffer.append(data) if self.size is not None: self.size = self.size - len(data) self.reset_timeout() return if self.size is not None and self.size > 0: return self.http.close() self.state += 1 def make_checksum(self, digest): return ''.join(['{0:02x}'.format(ord(x)) for x in digest]) ROOT_FILES = ['install.txt', 'delete.txt', 'readme.txt', 'thumbnail.png'] def adjust_path(self, filename): filename = get_normalized_path(filename) if filename in self.ROOT_FILES or os.path.dirname(filename): return filename return os.path.join('ghost', 'master', filename) def make_schedule(self): schedule = self.parse_updates2_dau() if schedule is not None: self.num_files = len(schedule) - 1 self.file_number = 0 if self.num_files >= 0: self.enqueue_event('OnUpdateReady', self.num_files) self.state += 1 return schedule def parse_updates2_dau(self): schedule = [] f = StringIO.StringIO(''.join(self.buffer)) for line in f: try: filename, checksum, newline = line.split('\001', 2) except ValueError: self.enqueue_event('OnUpdateFailure', 'broken updates2.dau') self.state = None return None if not filename: continue path = os.path.join(self.ghostdir, self.adjust_path( unicode(filename, 'Shift_JIS'))) try: with open(path, 'rb') as f: data = f.read() except IOError: # does not exist or broken pass else: m = hashlib.md5() m.update(data) if checksum == self.make_checksum(m.digest()): continue schedule.append((filename, checksum)) self.updated_files = [] return schedule def update_file(self, filename, checksum): data = ''.join(self.buffer) m = hashlib.md5() m.update(data) digest = self.make_checksum(m.digest()) if digest == checksum: path = os.path.join(self.ghostdir, self.adjust_path(filename)) subdir = os.path.dirname(path) if not os.path.exists(subdir): subroot = subdir while 1: head, tail = os.path.split(subroot) if os.path.exists(head): break else: subroot = head self.newdirs.append(subroot) try: os.makedirs(subdir) except OSError: self.enqueue_event( 'OnUpdateFailure', ''.join(("can't mkdir ", subdir))) self.state = None self.stop(revert=1) return if os.path.exists(path): if os.path.isfile(path): backup = ''.join((path, self.__BACKUP_SUFFIX)) os.rename(path, backup) self.backups.append(backup) else: self.newfiles.append(path) try: with open(path, 'wb') as f: try: f.write(data) except IOError: self.enqueue_event( 'OnUpdateFailure', ''.join(("can't write ", os.path.basename(path)))) self.state = None self.stop(revert=1) return except IOError: self.enqueue_event( 'OnUpdateFailure', ''.join(("can't open ", os.path.basename(path)))) self.state = None self.stop(revert=1) return self.updated_files.append(filename) event = 'OnUpdate.OnMD5CompareComplete' else: event = 'OnUpdate.OnMD5CompareFailure' self.enqueue_event(event, filename, checksum, digest) self.state = None self.stop(revert=1) return self.enqueue_event(event, filename, checksum, digest) self.file_number += 1 self.state += 1 def end_updates(self): filelist = self.parse_delete_txt() if filelist: for filename in filelist: path = os.path.join(self.ghostdir, filename) if os.path.exists(path) and os.path.isfile(path): try: os.unlink(path) logging.info('deleted {0}'.format(path)) except OSError as e: logging.error(e) update_list = ','.join(self.updated_files) if not update_list: self.enqueue_event('OnUpdateComplete', 'none') else: self.enqueue_event('OnUpdateComplete', 'changed', update_list) self.state = None self.stop() def parse_delete_txt(self): filelist = [] try: with open(os.path.join(self.ghostdir, 'delete.txt')) as f: for line in f: line = line.strip() if not line: continue filename = unicode(line, 'Shift_JIS') filelist.append(get_normalized_path(filename)) except IOError: return None return filelist def test(): import sys if len(sys.argv) != 3: raise SystemExit, 'Usage: update.py homeurl ghostdir\n' update = NetworkUpdate({'enqueu_event': lambda *a: None, 'check_event_queue': lambda *a: None,}) update.start(sys.argv[1], sys.argv[2], timeout=60) while 1: state = update.state s = time.time() code = update.run() e = time.time() delta = e - s if delta > 0.1: print 'Warning: state = {0:d} ({1:f} sec)'.format(state, delta) while 1: event = update.get_event() if not event: break print event if code == 0: break if update.state == 5 and update.schedule: print 'File(s) to be update:' for filename, checksum in update.schedule: print ' ', filename update.stop() if __name__ == '__main__': test() ninix-aya-4.3.9/lib/ninix/version.py000066400000000000000000000021331172114553600173620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2005-2012 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. # NUMBER = '4.3.9' CODENAME = 'juggling eggs' VERSION = '{0} ({1})'.format(NUMBER, CODENAME) VERSION_INFO = ( ''.join((r'\h\s[0]\w8ninix-aya {0}\n'.format(VERSION), unicode(_('Are igai No Nanika with "Nin\'i" for X'), 'utf-8'), r'\n', r'\_q' r'Copyright (c) 2001, 2002 Tamito KAJIYAMA\n' r'Copyright (c) 2002-2006 MATSUMURA Namihiko\n' r'Copyright (c) 2002-2012 Shyouzou Sugitani\n' r'Copyright (c) 2002, 2003 ABE Hideaki\n' r'Copyright (c) 2003-2005 Shun-ichi TAHARA\e')) ) ninix-aya-4.3.9/lib/ninix_main.py000066400000000000000000001516771172114553600167230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Copyright (C) 2001, 2002 by Tamito KAJIYAMA # Copyright (C) 2002, 2003 by MATSUMURA Namihiko # Copyright (C) 2002-2012 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. # import optparse import urlparse import urllib import os import socket import sys import logging import shutil import random import traceback import cStringIO import imp import locale import multiprocessing from collections import OrderedDict if os.name == 'nt': # locale setting for windows lang = os.getenv('LANG') if lang is None: os.environ['LANG'] = locale._build_localename(locale.getdefaultlocale()) import gettext gettext.install('ninix') import gtk import glib import pango import ninix.pix import ninix.home import ninix.prefs import ninix.sakura import ninix.sstp import ninix.communicate import ninix.ngm import ninix.lock import ninix.install import ninix.plugin import ninix.nekodorif import ninix.kinoko import ninix.menu from ninix.metamagic import Holon from ninix.metamagic import Meme USAGE = 'Usage: ninix [options]' parser = optparse.OptionParser(USAGE) parser.add_option('--sstp-port', type='int', dest='sstp_port', help='additional port for listening SSTP requests') parser.add_option('--debug', action='store_true', help='debug') parser.add_option('--logfile', type='string', help='logfile name') # XXX: check stderr - logger's default destination and redirect it if needed # (See http://bugs.python.org/issue706263 for more details.) try: tmp = os.dup(sys.__stderr__.fileno()) except: sys.stderr = file(os.devnull, 'w') else: os.close(tmp) logger = logging.getLogger() logger.setLevel(logging.INFO) # XXX def handleException(exception_type, value, tb): logger.error('Uncaught exception', exc_info=(exception_type, value, tb)) response_id = 1 dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_NONE, _('A ninix-aya error has been detected.')) dialog.set_title(_('Bug Detected')) dialog.set_position(gtk.WIN_POS_CENTER) dialog.set_gravity(gtk.gdk.GRAVITY_CENTER) button = dialog.add_button(_('Show Details'), response_id) dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) textview = gtk.TextView() textview.set_editable(False) left, top, scrn_w, scrn_h = ninix.pix.get_workarea() width = scrn_w / 2 height = scrn_h / 4 textview.set_size_request(width, height) textview.show() sw = gtk.ScrolledWindow() sw.show() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(textview) frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_IN) frame.add(sw) frame.set_border_width(7) dialog.vbox.add(frame) stringio = cStringIO.StringIO() traceback.print_exception(exception_type, value, tb, None, stringio) textbuffer = textview.get_buffer() textbuffer.set_text(stringio.getvalue()) while 1: if dialog.run() == response_id: frame.show() button.set_sensitive(0) else: # close button break dialog.destroy() raise SystemExit sys.excepthook = handleException def main(): if gtk.pygtk_version < (2,10,0): logging.critical('PyGtk 2.10.0 or later required') raise SystemExit # parse command line arguments (options, rest) = parser.parse_args() if rest: parser.error('Unknown option(s)') if options.logfile: logger.addHandler(logging.FileHandler(options.logfile)) # 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 options if options.sstp_port is not None: if options.sstp_port < 1024: logging.warning('Invalid --sstp-port number (ignored)') else: sstp_port.append(options.sstp_port) if options.debug is not None and options.debug: logger.setLevel(logging.DEBUG) home_dir = ninix.home.get_ninix_home() if not os.path.exists(home_dir): try: os.makedirs(home_dir) except: raise SystemExit, 'Cannot create Home directory (abort)\n' # aquire Inter Process Mutex (not Global Mutex) with open(os.path.join(ninix.home.get_ninix_home(), '.lock'), 'w') as f: try: ninix.lock.lockfile(f) except: raise SystemExit, 'ninix-aya is already running' # start logging.info('loading...') app = Application(sstp_port) logging.info('done.') app.run() try: ninix.lock.unlockfile(f) except: pass class SSTPControler(object): def __init__(self, sstp_port): self.request_parent = lambda *a: None # dummy self.sstp_port = sstp_port self.sstp_servers = [] self.__sstp_queue = [] self.__sstp_flag = 0 self.__current_sender = None def set_responsible(self, request_method): self.request_parent = request_method def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { } handler = handlers.get(event, getattr(self, event, None)) if handler is None: result = self.request_parent( event_type, event, *arglist, **argdict) else: result = handler(*arglist, **argdict) if event_type == 'GET': return result def enqueue_request(self, event, script_odict, sender, handle, address, show_sstp_marker, use_translator, entry_db, request_handler): self.__sstp_queue.append( (event, script_odict, sender, handle, address, show_sstp_marker, use_translator, entry_db, request_handler)) def check_request_queue(self, sender): count = 0 for request in self.__sstp_queue: if request[2].split(' / ')[0] == sender.split(' / ')[0]: count += 1 if self.__sstp_flag and \ self.__current_sender.split(' / ')[0] == sender.split(' / ')[0]: count += 1 return str(count), str(len(self.__sstp_queue)) def set_sstp_flag(self, sender): self.__sstp_flag = 1 self.__current_sender = sender def reset_sstp_flag(self): self.__sstp_flag = 0 self.__current_sender = None def handle_sstp_queue(self): if self.__sstp_flag or not self.__sstp_queue: return event, script_odict, sender, handle, address, \ show_sstp_marker, use_translator, \ entry_db, request_handler = self.__sstp_queue.pop(0) working = bool(event is not None) for if_ghost in script_odict.keys(): if if_ghost and self.request_parent('GET', 'if_ghost', if_ghost, working): self.request_parent('NOTIFY', 'select_current_sakura', if_ghost) default_script = script_odict[if_ghost] break else: if not self.request_parent('GET', 'get_preference', 'allowembryo'): if event is None: if request_handler: request_handler.send_response(420) # Refuse return else: default_script = None else: if '' in script_odict: # XXX default_script = script_odict[''] else: default_script = script_odict.values()[0] if event is not None: script = self.request_parent('GET', 'get_event_response', event) else: script = None if not script: script = default_script if script is None: if request_handler: request_handler.send_response(204) # No Content return self.set_sstp_flag(sender) self.request_parent( 'NOTIFY', 'enqueue_script', script, sender, handle, address, show_sstp_marker, use_translator, entry_db, request_handler, temp_mode=True) def receive_sstp_request(self): try: for sstp_server in self.sstp_servers: sstp_server.handle_request() except socket.error as e: code, message = e.args logging.error('socket.error: {0} ({1:d})'.format(message, code)) except ValueError: # may happen when ninix is terminated return def get_sstp_port(self): if not self.sstp_servers: return None return self.sstp_servers[0].server_address[1] def quit(self): for server in self.sstp_servers: server.close() def start_servers(self): for port in self.sstp_port: try: server = ninix.sstp.SSTPServer(('', port)) except socket.error as e: code, message = e.args logging.warning( 'Port {0:d}: {1} (ignored)'.format(port, message)) continue server.set_responsible(self.handle_request) self.sstp_servers.append(server) logging.info('Serving SSTP on port {0:d}'.format(port)) class PluginDialog: def __init__(self, plugin_dir, queue, message): self.plugin_dir = plugin_dir self.queue = queue dialog = gtk.Window() dialog.set_title(plugin_dir) ##dialog.set_decorated(False) ##dialog.set_resizable(False) dialog.connect('delete_event', self.delete) dialog.connect('key_press_event', self.key_press) dialog.connect('button_press_event', self.button_press) dialog.set_events(gtk.gdk.BUTTON_PRESS_MASK) ##dialog.set_modal(True) dialog.set_position(gtk.WIN_POS_CENTER) dialog.realize() entry = gtk.Entry() entry.connect('activate', self.activate) entry.set_size_request(320, -1) entry.show() box = gtk.VBox(spacing=10) box.set_border_width(10) label = gtk.Label(message) box.pack_start(label, False) label.show() box.pack_start(entry) dialog.add(box) box.show() self.dialog = dialog def open(self): self.dialog.show() def delete(self, widget, event): self.dialog.hide() self.cancel() return True def key_press(self, widget, event): if event.keyval == gtk.keysyms.Escape: self.dialog.hide() self.cancel() return True return False def button_press(self, widget, event): if event.button in [1, 2]: self.dialog.begin_move_drag( event.button, int(event.x_root), int(event.y_root), gtk.get_current_event_time()) return True def activate(self, widget): self.dialog.hide() self.enter(widget.get_text()) return True def enter(self, text): self.queue.put(text) def cancel(self): self.queue.put('') class PluginControler: def __init__(self): self.jobs = {} self.queue = {} self.data = {} self.dialog = {} self.request_parent = lambda *a: None # dummy def set_responsible(self, request_method): self.request_parent = request_method def save_data(self, plugin_dir): if plugin_dir not in self.data: return home_dir = ninix.home.get_ninix_home() target_dir = os.path.join(home_dir, 'plugin', plugin_dir) path = os.path.join(target_dir, 'SAVEDATA') with open(path, 'w') as f: f.write('#plugin: {0:1.1f}\n'.format(ninix.home.PLUGIN_STANDARD[1])) for name, value in self.data[plugin_dir].items(): if value is None: continue f.write('{0}:{1}\n'.format(name.encode('utf-8'), value.encode('utf-8'))) def load_data(self, plugin_dir): home_dir = ninix.home.get_ninix_home() target_dir = os.path.join(home_dir, 'plugin', plugin_dir) path = os.path.join(target_dir, 'SAVEDATA') if not os.path.exists(path): return {} data = {} try: with open(path, 'r') as f: line = f.readline() line = line.rstrip('\r\n') if not line.startswith('#plugin:'): return {} try: standard = float(line[8:]) except: return {} if standard < ninix.home.PLUGIN_STANDARD[0] or \ standard > ninix.home.PLUGIN_STANDARD[1]: return {} for line in f: line = line.rstrip('\r\n') line = unicode(line, 'utf-8') key, value = line.split(':', 1) data[key] = value except IOError as e: code, message = e.args logging.error('cannot read {0}'.format(path)) return data def terminate_plugin(self): for plugin_dir in self.data.keys(): self.save_data(plugin_dir) return ## FIXME def open_dialog(self, plugin_dir, message): self.dialog[plugin_dir] = PluginDialog(plugin_dir, self.queue[plugin_dir], message) self.dialog[plugin_dir].open() def check_queue(self): for plugin_dir in self.jobs.keys(): if not self.queue[plugin_dir].empty(): data = self.queue[plugin_dir].get() self.queue[plugin_dir].task_done() if isinstance(data, basestring): if data.startswith('DIALOG:'): message = data[7:] self.open_dialog(plugin_dir, message) continue elif not isinstance(data, dict): continue for name, value in data.items(): if not isinstance(name, basestring) or ':' in name or \ not (isinstance(value, basestring) or value is None): break else: assert plugin_dir in self.data self.data[plugin_dir].update(data) def exec_plugin(self, plugin_dir, argv, caller): if plugin_dir in self.jobs and self.jobs[plugin_dir].is_alive(): logging.warning('plugin {0} is already running'.format(plugin_dir)) return module_name, ext = os.path.splitext(argv[0]) home_dir = ninix.home.get_ninix_home() target_dir = os.path.join(home_dir, 'plugin', plugin_dir) module = self.__import_module(module_name, target_dir) if module is None: return port = self.request_parent('GET', 'get_sstp_port') queue = multiprocessing.JoinableQueue() if plugin_dir not in self.data: self.data[plugin_dir] = self.load_data(plugin_dir) data = self.data[plugin_dir] p = module.Plugin(port, target_dir, argv[1:], home_dir, caller, queue, data) if not isinstance(p, ninix.plugin.BasePlugin): return self.jobs[plugin_dir] = p self.queue[plugin_dir] = queue p.daemon = True p.start() if os.name == 'nt' and target_dir in sys.path: # XXX: an opposite of the BasePlugin.__init__ hack sys.path.remove(target_dir) def start_plugins(self, plugins): for plugin_name, plugin_dir, startup, menu_items in plugins: if startup is not None: self.exec_plugin(plugin_dir, startup, {'name': '', 'directory': ''}) def __import_module(self, name, directory): fp = None try: return reload(sys.modules[name]) except: pass try: fp, pathname, description = imp.find_module(name, [directory]) except: return None try: return imp.load_module(name, fp, pathname, description) finally: if fp: fp.close() return None class BalloonMeme(Meme): def __init__(self, key): Meme.__init__(self, key) self.request_parent = lambda *a: None # dummy def set_responsible(self, request_method): self.request_parent = request_method def create_menuitem(self, data): desc, balloon = data subdir = balloon['balloon_dir'][0] name = desc.get('name', subdir) return self.request_parent( 'GET', 'create_balloon_menuitem', name, self.key) def delete_by_myself(self): self.request_parent('NOTIFY', 'delete_balloon', self.key) class Ghost(Holon): def __init__(self, key): Holon.__init__(self, key) self.request_parent = lambda *a: None # dummy def set_responsible(self, request_method): self.request_parent = request_method def create_menuitem(self, data): return self.request_parent('GET', 'create_menuitem', self.key, data) def delete_by_myself(self): self.request_parent('NOTIFY', 'delete_ghost', self.key) def create_instance(self, data): return self.request_parent('GET', 'create_ghost', data) class Application(object): def __init__(self, sstp_port=[9801, 11000]): self.loaded = False self.confirmed = False self.console = Console(self) # create preference dialog self.prefs = ninix.prefs.PreferenceDialog() self.prefs.set_responsible(self.handle_request) self.sstp_controler = SSTPControler(sstp_port) self.sstp_controler.set_responsible(self.handle_request) # create usage dialog self.usage_dialog = UsageDialog() self.communicate = ninix.communicate.Communicate() # create plugin manager self.plugin_controler = PluginControler() self.plugin_controler.set_responsible(self.handle_request) # create ghost manager self.__ngm = ninix.ngm.NGM() self.__ngm.set_responsible(self.handle_request) self.current_sakura = None # create installer self.installer = ninix.install.Installer() # create popup menu self.__menu = ninix.menu.Menu() self.__menu.set_responsible(self.handle_request) self.__menu_owner = None self.ghosts = OrderedDict() odict_baseinfo = ninix.home.search_ghosts() for key, value in odict_baseinfo.items(): holon = Ghost(key) holon.set_responsible(self.handle_request) self.ghosts[key] = holon holon.baseinfo = value self.balloons = OrderedDict() odict_baseinfo = ninix.home.search_balloons() for key, value in odict_baseinfo.items(): meme = BalloonMeme(key) meme.set_responsible(self.handle_request) self.balloons[key] = meme meme.baseinfo = value self.balloon_menu = self.create_balloon_menu() self.plugins = ninix.home.search_plugins() self.nekoninni = ninix.home.search_nekoninni() self.katochan = ninix.home.search_katochan() self.kinoko = ninix.home.search_kinoko() def handle_request(self, event_type, event, *arglist, **argdict): assert event_type in ['GET', 'NOTIFY'] handlers = { 'close_all': self.close_all_ghosts, 'edit_preferences': self.prefs.edit_preferences, 'get_preference': self.prefs.get, 'get_otherghostname': self.communicate.get_otherghostname, 'rebuild_ghostdb': self.communicate.rebuild_ghostdb, 'reset_sstp_flag': self.sstp_controler.reset_sstp_flag, 'send_message': self.communicate.send_message, 'get_sstp_port': self.sstp_controler.get_sstp_port, } handler = handlers.get(event, getattr(self, event, lambda *a: None)) ## FIXME result = handler(*arglist, **argdict) if event_type == 'GET': return result def do_install(self, filename): try: filetype, target_dir = self.installer.install( filename, ninix.home.get_ninix_home()) except: target_dir = None if target_dir is not None: if filetype == 'ghost': self.add_sakura(target_dir) elif filetype == 'supplement': self.add_sakura(target_dir) # XXX: reload elif filetype == 'balloon': self.add_balloon(target_dir) elif filetype == 'plugin': self.plugins = ninix.home.search_plugins() elif filetype == 'nekoninni': self.nekoninni = ninix.home.search_nekoninni() elif filetype == 'katochan': self.katochan = ninix.home.search_katochan() elif filetype == 'kinoko': self.kinoko = ninix.home.search_kinoko() @property def current_sakura_instance(self): return self.ghosts[self.current_sakura].instance def create_ghost(self, data): ghost = ninix.sakura.Sakura() ghost.set_responsible(self.handle_request) ghost.new(*data) return ghost def get_sakura_cantalk(self): return self.current_sakura_instance.cantalk def get_event_response(self, event, *arglist, **argdict): ## FIXME return self.current_sakura_instance.get_event_response(*event) def keep_silence(self, quiet): self.current_sakura_instance.keep_silence(quiet) def get_ghost_name(self): ## FIXME sakura = self.current_sakura_instance return sakura.get_ifghost() def enqueue_event(self, event, *arglist, **argdict): self.current_sakura_instance.enqueue_event(event, *arglist, **argdict) def enqueue_script(self, script, sender, handle, host, show_sstp_marker, use_translator, db=None, request_handler=None, temp_mode=False): sakura = self.current_sakura_instance if temp_mode: sakura.enter_temp_mode() sakura.enqueue_script(script, sender, handle, host, show_sstp_marker, use_translator, db, request_handler) def get_working_ghost(self, cantalk=0): for value in self.ghosts.values(): sakura = value.instance if sakura is None: continue if not sakura.is_running(): continue if cantalk and not sakura.cantalk: continue yield sakura def getstring(self, name): return self.__menu_owner.getstring(name) def stick_window(self): stick = self.__menu.get_stick() self.__menu_owner.stick_window(stick) def toggle_bind(self, args): self.__menu_owner.toggle_bind(args) def select_shell(self, key): self.__menu_owner.select_shell(key) def select_balloon(self, key): desc, balloon = self.get_balloon_description(key) self.__menu_owner.select_balloon(key, desc, balloon) def get_current_balloon_directory(self): ## FIXME return self.__menu_owner.get_current_balloon_directory() def start_sakura_cb(self, key): ## FIXME self.start_sakura(key, init=1) # XXX def select_sakura(self, key): if self.__menu_owner.busy(): gtk.gdk.beep() return self.change_sakura(self.__menu_owner, key, 'manual') def notify_site_selection(self, args): self.__menu_owner.notify_site_selection(args) def close_sakura(self): self.__menu_owner.close() def about(self): self.__menu_owner.about() def vanish(self): self.__menu_owner.vanish() def network_update(self): self.__menu_owner.network_update() def open_popup_menu(self, sakura, button, side): self.__menu_owner = sakura path_background, path_sidebar, path_foreground = \ self.__menu_owner.get_menu_pixmap() self.__menu.set_pixmap(path_background, path_sidebar, path_foreground) background, foreground = self.__menu_owner.get_menu_fontcolor() self.__menu.set_fontcolor(background, foreground) mayuna_menu = self.__menu_owner.get_mayuna_menu() self.__menu.create_mayuna_menu(mayuna_menu) self.__menu.popup(button, side) def get_ghost_menus(self): for value in self.ghosts.values(): yield value.menuitem def get_shell_menu(self): return self.__menu_owner.get_shell_menu() def get_balloon_menu(self): current_key = self.get_current_balloon_directory() for key in self.balloons: menuitem = self.balloons[key].menuitem menuitem.set_sensitive(key != current_key) # not working return self.balloon_menu def create_balloon_menuitem(self, balloon_name, balloon_key): return self.__menu.create_meme_menuitem( balloon_name, balloon_key, self.select_balloon) def create_balloon_menu(self): balloon_menuitems = OrderedDict() for key in self.balloons.keys(): balloon_menuitems[key] = self.balloons[key].menuitem return self.__menu.create_meme_menu(balloon_menuitems) def create_shell_menu(self, menuitems): return self.__menu.create_meme_menu(menuitems) def create_shell_menuitem(self, shell_name, shell_key): return self.__menu.create_meme_menuitem( shell_name, shell_key, self.select_shell) def create_menuitem(self, key, baseinfo): desc = baseinfo[0] shiori_dir = baseinfo[1] icon = desc.get('icon', None) if icon is not None: if os.name == 'nt': # XXX: path should be encoded in mbcs on Windows icon = icon.encode('mbcs') icon_path = os.path.join(shiori_dir, icon) if not os.path.exists(icon_path): icon_path = None else: icon_path = None name = desc.get('name') start_menuitem = self.__menu.create_ghost_menuitem( name, icon_path, key, self.start_sakura_cb) # XXX select_menuitem = self.__menu.create_ghost_menuitem( name, icon_path, key, self.select_sakura) menuitem = { 'Summon': start_menuitem, 'Change': select_menuitem, } return menuitem def delete_ghost(self, key): assert key in self.ghosts del self.ghosts[key] def get_balloon_list(self): ## FIXME balloon_list = [] for key in self.balloons.keys(): desc, balloon = self.balloons[key].baseinfo subdir = balloon['balloon_dir'][0] name = desc.get('name', subdir) balloon_list.append((name, subdir)) return balloon_list def get_plugin_list(self): ## FIXME plugin_list = [] for i, plugin in enumerate(self.plugins): plugin_name = plugin[0] menu_items = plugin[3] if not menu_items: continue item = {} item['name'] = plugin_name item['icon'] = None item_list = [] for j, menu_item in enumerate(menu_items): label = menu_item[0] value = (i, j) item_list.append((label, value)) item['items'] = item_list plugin_list.append(item) return plugin_list def get_nekodorif_list(self): ## FIXME nekodorif_list = [] nekoninni = self.nekoninni for nekoninni_name, nekoninni_dir in nekoninni: if not nekoninni_name: continue item = {} item['name'] = nekoninni_name item['dir'] = nekoninni_dir nekodorif_list.append(item) return nekodorif_list def get_kinoko_list(self): ## FIXME return self.kinoko def load(self): # load user preferences self.prefs.load() # choose default ghost/shell directory = self.prefs.get('sakura_dir') name = self.prefs.get('sakura_name') # XXX: backward compat default_sakura = self.find_ghost_by_dir(directory) or \ self.find_ghost_by_name(name) or \ self.choose_default_sakura() # load ghost self.current_sakura = default_sakura ##for i, name in enumerate(self.get_ghost_names()): ## logging.info( ## 'GHOST({0:d}): {1}'.format( ## i, name.encode('utf-8', 'ignore'))) self.start_sakura(self.current_sakura, init=1) def find_ghost_by_dir(self, directory): return directory if directory in self.ghosts else None def find_ghost_by_name(self, name): for key in self.ghosts: sakura = self.ghosts[key].instance try: if sakura.get_name(default=None) == name: return key except: # old preferences(EUC-JP) pass return None def choose_default_sakura(self): return list(self.ghosts.keys())[0] def find_balloon_by_name(self, name): for key in self.balloons: desc, balloon = self.balloons[key].baseinfo try: if desc.get('name') == name: return key if balloon['balloon_dir'][0] == ninix.home.get_normalized_path(name): # XXX return key except: # old preferences(EUC-JP) pass return None def find_balloon_by_subdir(self, subdir): for key in self.balloons: desc, balloon = self.balloons[key].baseinfo try: if balloon['balloon_dir'][0] == subdir: return key if ninix.home.get_normalized_path(desc.get('name')) == subdir: # XXX return key except: # old preferences(EUC-JP) pass return None def run(self): self.timeout_id = glib.timeout_add(100, self.do_idle_tasks) # 100[ms] gtk.main() def get_ghost_names(self): for value in self.ghosts.values(): if value.instance is not None: yield value.instance.get_selfname() ## FIXME def if_ghost(self, if_ghost, working=False): for sakura in (value.instance for value in self.ghosts.values() \ if value.instance is not None): if working: if not sakura.is_running() or not sakura.cantalk: continue if sakura.ifghost(if_ghost): return 1 else: return 0 def update_sakura(self, name, sender): key = self.find_ghost_by_name(name) if key is None: return sakura = self.ghosts[key].instance if not sakura.is_running(): self.start_sakura(key, init=1) sakura.enqueue_script('\![updatebymyself]\e', sender, None, None, 0, 0, None) def select_current_sakura(self, ifghost=None): if ifghost is not None: for value in self.ghosts.values(): sakura = value.instance if sakura is None: continue if sakura.ifghost(ifghost): if not sakura.is_running(): self.current_sakura = value.key self.start_sakura(self.current_sakura, init=1, temp=1) ## FIXME else: self.current_sakura = sakura.key break else: pass else: return else: working_list = list(self.get_working_ghost(cantalk=1)) if working_list: self.current_sakura = random.choice(working_list).key else: return ## FIXME def set_menu_sensitive(self, key, flag): menuitems = self.ghosts[key].menuitem for item in menuitems.values(): item.set_sensitive(flag) def close_ghost(self, sakura): if not any(self.get_working_ghost()): self.prefs.set_current_sakura(sakura.key) self.quit() elif self.current_sakura == sakura.key: self.select_current_sakura() def close_all_ghosts(self): for sakura in self.get_working_ghost(): sakura.close() def quit(self): glib.source_remove(self.timeout_id) self.usage_dialog.close() self.sstp_controler.quit() ## FIXME self.plugin_controler.terminate_plugin() ## FIXME self.save_preferences() gtk.main_quit() def save_preferences(self): try: self.prefs.save() except IOError: logging.error('Cannot write preferences to file (ignored).') except: pass ## FIXME def select_ghost(self, sakura, sequential, event=1, vanished=0): keys = list(self.ghosts.keys()) if len(keys) < 2: return # select another ghost if sequential: key = (keys.index(sakura.key) + 1) % len(keys) else: keys.remove(sakura.key) key = random.choice(keys) self.change_sakura(sakura, key, 'automatic', event, vanished) def select_ghost_by_name(self, sakura, name, event=1): key = self.find_ghost_by_name(name) if key is None: return self.change_sakura(sakura, key, 'automatic', event) def change_sakura(self, sakura, key, method, event=1, vanished=0): if sakura.key == key: # XXX: needs reloading? return def proc(self=self, key=key): self.stop_sakura(sakura, self.start_sakura, key, sakura.key) if vanished: sakura.finalize() self.start_sakura(key, sakura.key, vanished) self.close_ghost(sakura) elif not event: proc() else: name = self.ghosts[key].instance.get_selfname(default='') sakura.enqueue_event('OnGhostChanging', name, method, proc=proc) def stop_sakura(self, sakura, starter=None, *args): sakura.finalize() if starter is not None: starter(*args) self.set_menu_sensitive(sakura.key, True) self.close_ghost(sakura) def start_sakura(self, key, prev=None, vanished=0, init=0, temp=0): sakura = self.ghosts[key].instance assert sakura is not None if prev is not None: assert prev in self.ghosts ## FIXME: vanish case? assert self.ghosts[prev].instance is not None if init: ghost_changed = 0 else: assert prev is not None ## FIXME if prev == key: ghost_changed = 0 else: ghost_changed = 1 if ghost_changed: name = self.ghosts[prev].instance.get_selfname() else: name = None sakura.notify_preference_changed() sakura.start(key, init, temp, vanished, ghost_changed, name) self.set_menu_sensitive(key, False) def notify_preference_changed(self): for sakura in self.get_working_ghost(): sakura.notify_preference_changed() def get_balloon_description(self, subdir): ## FIXME key = self.find_balloon_by_subdir(subdir) if key is None: ##logging.warning('Balloon {0} not found.'.format(name)) default_balloon = self.prefs.get('default_balloon') key = self.find_balloon_by_subdir(default_balloon) if key is None: key = self.balloons.keys()[0] return self.balloons[key].baseinfo def reload_current_sakura(self, sakura): self.save_preferences() key = sakura.key ghost_dir = os.path.split(sakura.get_prefix())[1] # XXX ghost_conf = ninix.home.search_ghosts([ghost_dir]) if ghost_conf: self.ghosts[key].baseinfo = ghost_conf[key] else: self.close_ghost(sakura) ## FIXME del self.ghosts[key] return ## FIXME self.start_sakura(key, key, init=1) def add_sakura(self, ghost_dir): if ghost_dir in self.ghosts: exists = 1 logging.warning('INSTALLED GHOST CHANGED: {0}'.format(ghost_dir)) else: exists = 0 logging.info('NEW GHOST INSTALLED: {0}'.format(ghost_dir)) ghost_conf = ninix.home.search_ghosts([ghost_dir]) if ghost_conf: if exists: sakura = self.ghosts[ghost_dir].instance if sakura.is_running(): # restart if working key = sakura.key def proc(self=self): self.ghosts[ghost_dir].baseinfo = ghost_conf[ghost_dir] logging.info('restarting....') self.start_sakura(key, key, init=1) logging.info('done.') self.stop_sakura(sakura, proc) else: holon = Ghost(ghost_dir) holon.set_responsible(self.handle_request) self.ghosts[ghost_dir] = holon holon.baseinfo = ghost_conf[ghost_dir] else: if exists: sakura = self.ghosts[ghost_dir].instance if sakura.is_running(): # stop if working self.stop_sakura(sakura) del self.ghosts[ghost_dir] def add_balloon(self, balloon_dir): if balloon_dir in self.balloons: exists = 1 logging.warning('INSTALLED BALLOON CHANGED: {0}'.format(balloon_dir)) else: exists = 0 logging.info('NEW BALLOON INSTALLED: {0}'.format(balloon_dir)) balloon_conf = ninix.home.search_balloons([balloon_dir]) if balloon_conf: if exists: self.balloons[balloon_dir].baseinfo = balloon_conf[balloon_dir] else: meme = BalloonMeme(balloon_dir) meme.set_responsible(self.handle_request) self.balloons[balloon_dir] = meme meme.baseinfo = balloon_conf[balloon_dir] else: if exists: del self.balloons[balloon_dir] self.balloon_menu = self.create_balloon_menu() def vanish_sakura(self, sakura): # remove ghost prefix = sakura.get_prefix() for filename in os.listdir(prefix): if os.path.isfile(os.path.join(prefix, filename)): if filename != 'HISTORY': try: os.remove(os.path.join(prefix, filename)) except: logging.error( '*** REMOVE FAILED *** : {0}'.format(filename)) else: # dir try: shutil.rmtree(os.path.join(prefix, filename)) except: logging.error( '*** REMOVE FAILED *** : {0}'.format(filename)) self.select_ghost(sakura, 0, vanished=1) del self.ghosts[sakura.key] def select_plugin(self, item): target = self.__menu_owner i, j = item plugin_name, plugin_dir, startup, menu_items = self.plugins[i] label, argv = menu_items[j] caller = {} caller['name'] = target.get_name() caller['directory'] = target.get_prefix() caller['ifghost'] = target.get_ifghost() self.plugin_controler.exec_plugin(plugin_dir, argv, caller) def select_nekodorif(self, nekodorif_dir): target = self.__menu_owner ninix.nekodorif.Nekoninni().load(nekodorif_dir, self.katochan, target) def select_kinoko(self, data): target = self.__menu_owner ninix.kinoko.Kinoko(self.kinoko).load(data, target) def open_console(self): self.console.open() def open_ghost_manager(self): self.__ngm.show_dialog() def show_usage(self): for sakura in self.get_working_ghost(): sakura.save_history() history = {} for key in self.ghosts: sakura = self.ghosts[key].instance name = sakura.get_name(default=key) ghost_time = 0 prefix = sakura.get_prefix() path = os.path.join(prefix, 'HISTORY') if os.path.exists(path): try: with open(path, 'r') as f: for line in f: if ',' not in line: continue key, value = line.split(',', 1) key = key.strip() if key == 'time': try: ghost_time = int(value.strip()) except: pass except IOError as e: code, message = e.args logging.error('cannot read {0}'.format(path)) ai_list = [] dirlist = os.listdir(os.path.join(prefix, 'shell')) for subdir in dirlist: path = os.path.join(prefix, 'shell', subdir, 'ai.png') if os.path.exists(path): ai_list.append(path) history[name] = (ghost_time, ai_list) self.usage_dialog.open(history) def search_ghosts(self): ## FIXME balloons = self.balloons ## FIXME ghosts = self.ghosts ## FIXME if len(ghosts) > 0 and len(balloons) > 0: self.confirmed = True return len(ghosts), len(balloons) def do_idle_tasks(self): if not self.confirmed: self.console.open() else: if not self.loaded: self.load() # start SSTP server self.sstp_controler.start_servers() # start plugins self.plugin_controler.start_plugins(self.plugins) self.loaded = True else: self.sstp_controler.handle_sstp_queue() self.sstp_controler.receive_sstp_request() self.plugin_controler.check_queue() return True class Console(object): # DnD data types dnd_targets = [ ('text/plain', 0, 0), ] def __init__(self, app): self.app = app self.window = gtk.Dialog() self.window.connect('delete_event', self.close) self.darea = gtk.DrawingArea() self.darea.set_events(gtk.gdk.EXPOSURE_MASK) self.darea.connect('drag_data_received', self.drag_data_received) self.darea.drag_dest_set(gtk.DEST_DEFAULT_ALL, self.dnd_targets, gtk.gdk.ACTION_COPY) self.size = (330, 110) ## FIXME self.darea.set_size_request(*self.size) self.darea.connect('configure_event', self.configure) self.darea.connect('expose_event', self.redraw) self.window.vbox.pack_start(self.darea) self.darea.show() box = gtk.HButtonBox() self.window.action_area.pack_start(box) box.show() button = gtk.Button('Install') button.connect('clicked', self.file_chooser) box.add(button) button.show() button = gtk.Button('Close') button.connect('clicked', self.close) box.add(button) button.show() self.file_chooser = gtk.FileChooserDialog( "Install..", None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_OPEN, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) self.file_chooser.set_default_response(gtk.RESPONSE_CANCEL) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") self.file_chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("nar/zip") filter.add_mime_type("application/zip") filter.add_pattern("*.nar") filter.add_pattern("*.zip") self.file_chooser.add_filter(filter) self.opened = 0 def update(self): ## FIXME self.darea.queue_draw() def open(self): if self.opened: return self.window.show() self.opened = 1 def close(self, widget=None, event=None): self.window.hide() self.opened = 0 if not self.app.confirmed: ## FIXME self.app.quit() return True def file_chooser(self, widget=None, event=None): response = self.file_chooser.run() if response == gtk.RESPONSE_OK: filename = self.file_chooser.get_filename() self.app.do_install(filename) self.update() elif response == gtk.RESPONSE_CANCEL: pass self.file_chooser.hide() def configure(self, darea, event): x, y, w, h = darea.get_allocation() self.size = (w, h) def draw_message(self, text): ## FIXME pass def redraw(self, darea, event): ghosts, balloons = self.app.search_ghosts() # XXX if ghosts > 0 and balloons > 0: self.window.set_title(_('Console')) else: self.window.set_title(_('Nanntokashitekudasai.')) layout = pango.Layout(darea.get_pango_context()) font_desc = pango.FontDescription() font_desc.set_size(9 * pango.SCALE) font_desc.set_family('Sans') # FIXME layout.set_font_description(font_desc) cr = darea.window.cairo_create() w, h = self.size cr.set_source_rgb(0.0, 0.0, 0.0) # black cr.paint() layout.set_text('Ghosts: {0:d}'.format(ghosts)) if ghosts == 0: cr.set_source_rgb(1.0, 0.2, 0.2) # red else: cr.set_source_rgb(0.8, 0.8, 0.8) # gray cr.move_to(20, 15) cr.show_layout(layout) w, h = layout.get_pixel_size() layout.set_text('Balloons: {0:d}'.format(balloons)) if balloons == 0: cr.set_source_rgb(1.0, 0.2, 0.2) # red else: cr.set_source_rgb(0.8, 0.8, 0.8) # gray cr.move_to(20, 15 + h) cr.show_layout(layout) del cr def drag_data_received(self, widget, context, x, y, data, info, time): logging.info('Content-type: {0}'.format(data.type)) logging.info('Content-length: {0:d}'.format(data.get_length())) if str(data.type) == 'text/plain': filelist = [] for line in data.data.split('\r\n'): scheme, host, path, params, query, fragment = \ urlparse.urlparse(line) pathname = urllib.url2pathname(path) if scheme == 'file' and os.path.exists(pathname): filelist.append(pathname) for filename in filelist: self.app.do_install(filename) self.update() return True class UsageDialog(object): def __init__(self): self.window = gtk.Dialog() self.window.set_title('Usage') self.window.connect('delete_event', self.close) self.darea = gtk.DrawingArea() self.darea.set_events(gtk.gdk.EXPOSURE_MASK) self.size = (550, 330) self.darea.set_size_request(*self.size) self.darea.connect('configure_event', self.configure) self.darea.connect('expose_event', self.redraw) self.window.vbox.pack_start(self.darea) self.darea.show() box = gtk.HButtonBox() box.set_layout(gtk.BUTTONBOX_END) self.window.action_area.pack_start(box) box.show() button = gtk.Button('Close') button.connect('clicked', self.close) box.add(button) button.show() self.opened = 0 def open(self, history): if self.opened: return self.history = history self.items = \ [(name, clock, path) for name, (clock, path) in \ self.history.items()] self.items[:] = [(x[1], x) for x in self.items] self.items.sort() self.items[:] = [x for x_1, x in self.items] self.items.reverse() ai_list = self.items[0][2] if ai_list: path = random.choice(ai_list) assert os.path.exists(path) self.pixbuf = ninix.pix.create_pixbuf_from_file( path, is_pnr=False) self.pixbuf.saturate_and_pixelate(self.pixbuf, 1.0, True) else: self.pixbuf = None self.window.show() self.opened = 1 def close(self, widget=None, event=None): self.window.hide() self.opened = 0 return True def configure(self, darea, event): x, y, w, h = darea.get_allocation() self.size = (w, h) def redraw(self, darea, event): if not self.items: return # should not reach here total = float(0) for name, clock, path in self.items: total += clock layout = pango.Layout(darea.get_pango_context()) font_desc = pango.FontDescription() font_desc.set_size(9 * pango.SCALE) font_desc.set_family('Sans') # FIXME layout.set_font_description(font_desc) cr = darea.window.cairo_create() # redraw graph w, h = self.size cr.set_source_rgb(1.0, 1.0, 1.0) # white cr.paint() # ai.png if self.pixbuf: cr.set_source_pixbuf(self.pixbuf, 16, 32) # XXX cr.paint() w3 = w4 = 0 rows = [] for name, clock, path in self.items[:14]: layout.set_text(name) name_w, name_h = layout.get_pixel_size() rate = '{0:.1f}%'.format(clock / total * 100) layout.set_text(rate) rate_w, rate_h = layout.get_pixel_size() w3 = max(rate_w, w3) time = '{0}:{1:02d}'.format(*divmod(clock / 60, 60)) layout.set_text(time) time_w, time_h = layout.get_pixel_size() w4 = max(time_w, w4) rows.append((clock, name, name_w, name_h, rate, rate_w, rate_h, time, time_w, time_h)) 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.get_pixel_size() cr.set_source_rgb(0.8, 0.8, 0.8) # gray cr.move_to(x, y) cr.show_layout(layout) x = x + w2 + 10 label = 'rate' layout.set_text(label) label_rate_w, label_rate_h = layout.get_pixel_size() cr.set_source_rgb(0.8, 0.8, 0.8) # gray cr.move_to(x + w3 - label_rate_w, y) cr.show_layout(layout) x += w3 + 10 label = 'time' layout.set_text(label) label_time_w, label_time_h = layout.get_pixel_size() cr.set_source_rgb(0.8, 0.8, 0.8) # gray cr.move_to(x + w4 - label_time_w, y) cr.show_layout(layout) y += max([label_name_h, label_rate_h, label_time_h]) + 4 for clock, name, name_w, name_h, rate, rate_w, rate_h, time, time_w, \ time_h in rows: x = 20 bw = int(clock / total * w1) bh = max([name_h, rate_h, time_h]) - 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 = len(name) while end > 0: w, h = layout.get_pixel_size() if w > 168: end -= 1 layout.set_text(''.join((name[:end], u'...'))) else: break cr.set_source_rgb(0.0, 0.0, 0.0) # black cr.move_to(x, y) cr.show_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_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_layout(layout) y += max([name_h, rate_h, time_h]) + 4 del cr if __name__ == '__main__': main() ninix-aya-4.3.9/locale/000077500000000000000000000000001172114553600146705ustar00rootroot00000000000000ninix-aya-4.3.9/locale/ja.po000066400000000000000000000203221172114553600156210ustar00rootroot00000000000000# ja.po file for ninix-aya # Copyright (C) 2003-2011 Shyouzou Sugitani # msgid "" msgstr "" "Project-Id-Version: ninix-aya 4.2\n" "POT-Creation-Date: Tue Sep 23 18:01:59 2003\n" "PO-Revision-Date: 2011-10-16 02:11+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" "Generated-By: pygettext.py 1.5\n" #: lib/ninix/kinoko.py:49 msgid "Skin(_K)" msgstr "スキン(_K)" #: lib/ninix/kinoko.py:51 lib/ninix/nekodorif.py:82 msgid "Exit(_Q)" msgstr "終了(_Q)" #: lib/ninix/menu.py:73 msgid "Portal sites(_P)" msgstr "ポータル(_P)" #: lib/ninix/menu.py:76 msgid "Recommend sites(_R)" msgstr "おすすめ(_R)" #: lib/ninix/menu.py:79 msgid "Options(_F)" msgstr "設定(_F)" #: lib/ninix/menu.py:82 msgid "Network Update(_U)" msgstr "ネットワーク更新(_U)" #: lib/ninix/menu.py:86 msgid "Vanish(_F)" msgstr "消滅指示(_F)" #: lib/ninix/menu.py:90 msgid "Preferences...(_O)" msgstr "設定(_O)" #: lib/ninix/menu.py:94 msgid "Console(_C)" msgstr "コンソール(_C)" #: lib/ninix/menu.py:98 msgid "Ghost Manager(_M)" msgstr "ゴーストマネージャ(_M)" #: lib/ninix/menu.py:102 msgid "Information(_I)" msgstr "情報(_I)" #: lib/ninix/menu.py:105 msgid "Usage graph(_A)" msgstr "使用率グラフ(_A)" #: lib/ninix/menu.py:109 msgid "Version(_V)" msgstr "バージョン情報(_V)" #: lib/ninix/menu.py:113 msgid "Close(_W)" msgstr "終了(_W)" #: lib/ninix/menu.py:116 msgid "Quit(_Q)" msgstr "全て終了(_Q)" #: lib/ninix/menu.py:120 msgid "Change(_G)" msgstr "交代(_G)" #: lib/ninix/menu.py:123 msgid "Summon(_X)" msgstr "召喚(_X)" #: lib/ninix/menu.py:126 msgid "Shell(_S)" msgstr "シェル(_S)" #: lib/ninix/menu.py:129 msgid "Balloon(_B)" msgstr "バルーン(_B)" #: lib/ninix/menu.py:132 msgid "Costume(_C)" msgstr "着せ替え(_C)" #: lib/ninix/menu.py:135 msgid "Stick(_Y)" msgstr "デスクトップに居座る(_Y)" #: lib/ninix/menu.py:140 msgid "Nekodorif(_N)" msgstr "猫どりふ(_N)" #: lib/ninix/menu.py:143 msgid "Kinoko(_K)" msgstr "きのこ(_K)" #: lib/ninix/menu.py:146 msgid "Plugin(_P)" msgstr "プラグイン(_P)" #: lib/ninix/nekodorif.py:77 msgid "Settings...(_O)" msgstr "設定...(_O)" #: lib/ninix/nekodorif.py:80 msgid "Katochan(_K)" msgstr "落下物(_K)" #: lib/ninix/ngm.py:263 msgid "Search for" msgstr "検索文字を入力して下さい" #: lib/ninix/ngm.py:269 msgid "OK" msgstr "OK" #: lib/ninix/ngm.py:272 msgid "Cancel" msgstr "キャンセル" #: lib/ninix/ngm.py:334 msgid "_File" msgstr "ファイル(_F)" #: lib/ninix/ngm.py:335 msgid "_View" msgstr "表示(_V)" #: lib/ninix/ngm.py:336 msgid "_Archive" msgstr "アーカイブ(_A)" #: lib/ninix/ngm.py:337 msgid "_Help" msgstr "ヘルプ(_H)" #: lib/ninix/ngm.py:339 msgid "Search(_F)" msgstr "検索(_F)" #: lib/ninix/ngm.py:343 msgid "Search Forward(_S)" msgstr "次を検索(_S)" #: lib/ninix/ngm.py:347 msgid "Settings(_O)" msgstr "設定(_O)" #: lib/ninix/ngm.py:352 msgid "DB Network Update(_N)" msgstr "DB ネットワーク更新(_N)" #: lib/ninix/ngm.py:356 msgid "Close(_X)" msgstr "終了(_X)" #: lib/ninix/ngm.py:360 msgid "Mask(_M)" msgstr "表示マスク(_M)" #: lib/ninix/ngm.py:365 msgid "Reset to Default(_Y)" msgstr "デフォルトに戻す(_Y)" #: lib/ninix/ngm.py:370 msgid "Show All(_Z)" msgstr "すべて表示(_Z)" #: lib/ninix/ngm.py:405 msgid "Ghost Manager" msgstr "ゴーストマネージャ" #: lib/ninix/ngm.py:439 msgid "Previous" msgstr "前ヘ" #: lib/ninix/ngm.py:444 msgid "Next" msgstr "次ヘ" #: lib/ninix/ngm.py:551 msgid "Install" msgstr "Install" #: lib/ninix/ngm.py:558 msgid "Update" msgstr "Update" #: lib/ninix/ngm.py:595 msgid "Author:" msgstr "作者:" #: lib/ninix/ngm.py:596 msgid "ArchiveTime:" msgstr "最終更新時刻:" #: lib/ninix/ngm.py:597 msgid "ArchiveSize:" msgstr "アーカイブサイズ:" #: lib/ninix/ngm.py:598 msgid "NetworkUpdateTime:" msgstr "ネットワーク更新時刻:" #: lib/ninix/ngm.py:599 msgid "Version:" msgstr "バージョン:" #: lib/ninix/ngm.py:600 msgid "AIName:" msgstr "使用偽AI:" #: lib/ninix/ngm.py:608 lib/ninix/ngm.py:613 msgid "SurfaceList:" msgstr "使用サーフィス:" #: lib/ninix/ngm.py:625 msgid " Web Page" msgstr "公開ページ" #: lib/ninix/prefs.py:125 msgid "Font" msgstr "フォント" #: lib/ninix/prefs.py:126 msgid "Surface&Balloon" msgstr "サーフェス&バルーン" #: lib/ninix/prefs.py:127 msgid "Misc" msgstr "色々" #: lib/ninix/prefs.py:128 msgid "Debug" msgstr "デバッグ" #: lib/ninix/prefs.py:261 msgid "Font(s) for balloons" msgstr "バルーンフォント" #: lib/ninix/prefs.py:275 msgid "Surface Scaling" msgstr "サーフェス倍率" #: lib/ninix/prefs.py:286 lib/ninix/prefs.py:399 msgid "Default Setting" msgstr "デフォルト設定" #: lib/ninix/prefs.py:294 msgid "Scale Balloon" msgstr "バルーンもいっしょ" #: lib/ninix/prefs.py:298 msgid "Default Balloon" msgstr "デフォルトのバルーン" #: lib/ninix/prefs.py:313 msgid "Balloon Name" msgstr "バルーン名" #: lib/ninix/prefs.py:319 msgid "Always Use This Balloon" msgstr "常にこのバルーンを使う" #: lib/ninix/prefs.py:323 msgid "Translucency" msgstr "透過処理" #: lib/ninix/prefs.py:331 msgid "Use PNA file" msgstr "PNAファイルを使用する" #: lib/ninix/prefs.py:338 msgid "Surfaces' alpha channel" msgstr "サーフェスの透過率" #: lib/ninix/prefs.py:348 msgid "Balloons' alpha channel" msgstr "バルーンの透過率" #: lib/ninix/prefs.py:355 msgid "Animation" msgstr "アニメーション" #: lib/ninix/prefs.py:366 msgid "Quality" msgstr "品質" #: lib/ninix/prefs.py:374 msgid "SERIKO INACTIVE" msgstr "アニメーション抑制" #: lib/ninix/prefs.py:384 msgid "SSTP Setting" msgstr "SSTP 設定" #: lib/ninix/prefs.py:388 msgid "Allowembryo" msgstr "IfGhostに一致するゴーストがいない場合に他のゴーストで再生(SEND/1.4)" #: lib/ninix/prefs.py:392 msgid "Script Wait" msgstr "表示ウェイト" #: lib/ninix/prefs.py:405 msgid "None" msgstr "なし" #: lib/ninix/prefs.py:407 msgid "Fast" msgstr "速い" #: lib/ninix/prefs.py:409 msgid "Slow" msgstr "遅い" #: lib/ninix/prefs.py:415 msgid "Raise & Lower" msgstr "Raise & Lower" #: lib/ninix/prefs.py:423 msgid "Sink after Talk" msgstr "喋り終わると裏へ沈む" #: lib/ninix/prefs.py:427 msgid "Raise before Talk" msgstr "喋る時手前に出てくる" #: lib/ninix/prefs.py:437 msgid "Surface Debugging" msgstr "サーフェスのデバッグ" #: lib/ninix/prefs.py:441 msgid "Display Collision Area" msgstr "当たり判定領域を表示する" #: lib/ninix/sakura.py:346 msgid "Network Update has begun." msgstr "ネットワーク更新を開始しました。" #: lib/ninix/sakura.py:350 msgid "Network Update completed successfully." msgstr "ネットワーク更新に成功しました。" #: lib/ninix/sakura.py:354 msgid "Network Update failed." msgstr "ネットワーク更新に失敗しました。" #: lib/ninix/sakura.py:463 msgid "Sakura&Unyuu" msgstr "さくら&うにゅう" #: lib/ninix/sakura.py:468 msgid "User" msgstr "ユーザーさん" #: lib/ninix/sakura.py:472 lib/ninix/sakura.py:476 msgid "Sakura" msgstr "さくら" #: lib/ninix/sakura.py:480 msgid "Unyuu" msgstr "うにゅう" #: lib/ninix/sakura.py:485 msgid "Tomoyo" msgstr "知世" #: lib/ninix/sakura.py:1872 msgid "I'm afraid I don't have Network Update yet." msgstr "ネットワーク更新、\\w4まだ無いの。" #: lib/ninix/sakura.py:1897 msgid "Vanish" msgstr "消滅指示" #: lib/ninix/sakura.py:1904 msgid "Yes" msgstr "はい" #: lib/ninix/sakura.py:1908 msgid "No" msgstr "いいえ" #: lib/ninix/version.py:19 msgid "Are igai No Nanika with \"Nin'i\" for X" msgstr "あれ以外の何か with \"任意\" for X" #: lib/ninix_main.py:72 msgid "A ninix-aya error has been detected." msgstr "母ちゃん…勘弁!!!!" #: lib/ninix_main.py:73 msgid "Bug Detected" msgstr "だからいったじゃないカーッ!!!" #: lib/ninix_main.py:76 msgid "Show Details" msgstr "詳細" #: lib/ninix_main.py:1024 msgid "Console" msgstr "コンソール" #: lib/ninix_main.py:1026 msgid "Nanntokashitekudasai." msgstr "何とかしてください" ninix-aya-4.3.9/locale/zh_TW.po000066400000000000000000000103031172114553600162600ustar00rootroot00000000000000# zh_TW.po file for ninix-aya # Copyright (C) 2003 Chieh-Nan Wang # msgid "" msgstr "" "Project-Id-Version: ninix-aya 2.1.1\n" "POT-Creation-Date: Wed Jul 16 17:32:26 2003\n" "Last-Translator: Chieh-Nan Wang \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.4\n" #: lib/ninix/main.py:35 msgid "Are igai No Nanika with \"Nin'i\" for X" msgstr "あれ以外の何か with \"任意\" for X" #: lib/ninix/main.py:301 lib/ninix/main.py:759 msgid "Network Update" msgstr "網路更新" #: lib/ninix/main.py:306 lib/ninix/main.py:773 lib/ninix/main.py:1677 msgid "Vanish" msgstr "消滅" #: lib/ninix/main.py:311 msgid "Reload" msgstr "再讀取" #: lib/ninix/main.py:313 msgid "Settings..." msgstr "設定..." #: lib/ninix/main.py:315 msgid "Usage graph" msgstr "使用率圖表" #: lib/ninix/main.py:316 msgid "Version" msgstr "版本情報" #: lib/ninix/main.py:318 msgid "Exit" msgstr "結束" #: lib/ninix/main.py:319 msgid "File" msgstr "檔案" #: lib/ninix/main.py:340 lib/ninix/main.py:388 lib/ninix/main.py:1106 msgid "Ghost" msgstr "Ghost" #: lib/ninix/main.py:400 lib/ninix/main.py:430 msgid "Shell" msgstr "Shell" #: lib/ninix/main.py:443 lib/ninix/main.py:452 msgid "Balloon" msgstr "Balloon" #: lib/ninix/main.py:481 msgid "Surface Scaling" msgstr "顯示倍率" #: lib/ninix/main.py:489 msgid "Costume" msgstr "換衣服" #: lib/ninix/main.py:501 msgid "None" msgstr "沒有" #: lib/ninix/main.py:502 msgid "Fast" msgstr "快速" #: lib/ninix/main.py:508 msgid "Slow" msgstr "慢" #: lib/ninix/main.py:516 msgid "Script Wait" msgstr "說話停頓" #: lib/ninix/main.py:551 msgid "Plugin" msgstr "外掛" #: lib/ninix/main.py:575 lib/ninix/main.py:872 msgid "Master" msgstr "主要" #: lib/ninix/main.py:1165 msgid "Event" msgstr "事件" #: lib/ninix/main.py:1166 msgid "Font" msgstr "字型" #: lib/ninix/main.py:1167 msgid "Mouse" msgstr "滑鼠" #: lib/ninix/main.py:1168 lib/ninix/main.py:1300 msgid "Browser" msgstr "瀏覽器" #: lib/ninix/main.py:1169 msgid "Helper" msgstr "小幫手" #: lib/ninix/main.py:1203 msgid "Event(s) to be ignored" msgstr "忽視事件" #: lib/ninix/main.py:1238 msgid "Font(s) for balloons" msgstr "balloon使用字型" #: lib/ninix/main.py:1250 msgid "Left button" msgstr "左鍵動作" #: lib/ninix/main.py:1258 msgid "delete balloon(s)" msgstr "刪除ballon" #: lib/ninix/main.py:1261 lib/ninix/main.py:1283 msgid "raise all windows" msgstr "顯示所有視窗" #: lib/ninix/main.py:1264 lib/ninix/main.py:1286 msgid "lower all windows" msgstr "影藏所有視窗" #: lib/ninix/main.py:1272 msgid "Right button" msgstr "右鍵動作" #: lib/ninix/main.py:1280 msgid "delete balloons" msgstr "刪除balloon" #: lib/ninix/main.py:1320 msgid "- %s in this command line will be replaced with the URL" msgstr "-在命令列中的 %s 會被替換成網址" #: lib/ninix/main.py:1324 lib/ninix/main.py:1389 msgid "- trailing & is not required.(automagically added)" msgstr "-不需要末尾的 & (會自動加入)" #: lib/ninix/main.py:1333 msgid "Application" msgstr "應用程式" #: lib/ninix/main.py:1385 msgid "- %s in this command line will be replaced with the filename" msgstr "-在命令列中的 %s 會被替換成檔名" #: lib/ninix/main.py:1684 msgid "Yes" msgstr "是的" #: lib/ninix/main.py:1688 msgid "No" msgstr "不是" #: lib/ninix/sakura.py:415 msgid "Network Update has begun." msgstr "已經開始網路更新。" #: lib/ninix/sakura.py:416 msgid "Network Update completed successfully." msgstr "網路更新成功。" #: lib/ninix/sakura.py:417 msgid "Network Update failed." msgstr "網路更新失敗了。" #: lib/ninix/sakura.py:1435 msgid "Sakura&Unyuu" msgstr "さくら&うにゅう" #: lib/ninix/sakura.py:1437 msgid "User" msgstr "使用者" #: lib/ninix/sakura.py:1439 lib/ninix/sakura.py:1441 msgid "Sakura" msgstr "さくら" #: lib/ninix/sakura.py:1443 msgid "Unyuu" msgstr "うにゅう" #: lib/ninix/sakura.py:1445 msgid "Tomoyo" msgstr "知世" #: lib/ninix/sakura.py:1551 msgid "I'm afraid I don't have Network Update yet." msgstr "恐怕現在沒有網路更新" ninix-aya-4.3.9/ninix_win32_postinst.py000066400000000000000000000044101172114553600201140ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys if sys.platform != 'win32': raise SystemExit, 'This script is meant to be run by the Windows installer, not directly from the command line.' if sys.argv[1] == '-install': # Important Notice!! # From inside the installer scripts MUST NOT call sys.exit() or # raise SystemExit, otherwise not only the script but also # the installer will terminate and leave things unmaintained. prefix = sys.prefix python_path = os.path.join(prefix, 'python.exe') pythonw_path = os.path.join(prefix, 'pythonw.exe') # ninix_dir = os.path.join(prefix, 'Lib/site-packages', 'ninix') # ico_path = os.path.join(ninix_dir, 'ninix-aya.ico') script_path = os.path.join(prefix, 'Scripts', 'ninix_main.py') try: desktop_path = get_special_folder_path('CSIDL_COMMON_DESKTOPDIRECTORY') except OSError: desktop_path = get_special_folder_path('CSIDL_DESKTOPDIRECTORY') create_shortcut(pythonw_path, 'ninix-aya', os.path.join(desktop_path, 'ninix-aya.lnk'), script_path, # ninix_dir, # ico_path ) file_created(os.path.join(desktop_path, 'ninix-aya.lnk')) try: start_path = get_special_folder_path('CSIDL_COMMON_PROGRAMS') except OSError: start_path = get_special_folder_path('CSIDL_PROGRAMS') programs_path = os.path.join(start_path, 'ninix-aya') try : os.mkdir(programs_path) except OSError: pass directory_created(programs_path) create_shortcut(pythonw_path, 'ninix-aya', os.path.join(programs_path, 'ninix-aya.lnk'), script_path, # ninix_dir, # ico_path ) file_created(os.path.join(programs_path, 'ninix-aya.lnk')) create_shortcut(python_path, 'ninix-aya (with python terminal)', os.path.join(programs_path, 'ninix-aya-wpt.lnk'), script_path, # ninix_dir, # ico_path ) file_created(os.path.join(programs_path, 'ninix-aya-wpt.lnk')) print 'Finished creating shortcuts.' ninix-aya-4.3.9/setup.py000066400000000000000000000021321172114553600151410ustar00rootroot00000000000000import os import sys from distutils.core import setup # Create mo files: if not os.path.exists('mo/'): os.mkdir('mo/') python_path = sys.prefix py_path = os.path.abspath(os.path.join(python_path, 'python.exe')) msgfmt_path = os.path.abspath(os.path.join(python_path, 'Tools/i18n/msgfmt.py')) langs = (l[:-3] for l in os.listdir('locale') if l.endswith('.po')) for lang in langs: pofile = os.path.join('locale', '{0}.po'.format(lang)) modir = os.path.join('mo', lang) mofile = os.path.join(modir, 'ninix.mo') if not os.path.exists(modir): os.mkdir(modir) print 'generating', mofile os.system('{0} {1} -o {2} {3}'.format(py_path, msgfmt_path, mofile, pofile)) setup( name = 'ninix-aya', version = '4.3.9', package_dir = {'': 'lib'}, packages=['ninix', 'ninix.dll'], scripts = ['lib/ninix_main.py', 'ninix_win32_postinst.py'], data_files = [('share/locale/ja/LC_MESSAGES', ['mo/ja/ninix.mo']), ('share/locale/zh_TW/LC_MESSAGES', ['mo/zh_TW/ninix.mo']),], options = {'bdist_wininst': {'install_script': 'ninix_win32_postinst.py'}} )