Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar
This commit is contained in:
commit
63f987594b
@ -69,7 +69,9 @@ services/console/lib/Makefile
|
||||
services/console/lib/graphics/Makefile
|
||||
services/console/lib/procmem/Makefile
|
||||
services/console/lib/net/Makefile
|
||||
services/console/lib/ui/Makefile
|
||||
services/console/lib/gui/Makefile
|
||||
services/console/lib/purk/Makefile
|
||||
services/console/lib/purk/scripts/Makefile
|
||||
services/console/Makefile
|
||||
services/console/interface/Makefile
|
||||
services/console/interface/xo/Makefile
|
||||
@ -82,6 +84,7 @@ services/console/interface/memphis/Makefile
|
||||
services/console/interface/network/Makefile
|
||||
services/console/interface/logviewer/Makefile
|
||||
services/console/interface/terminal/Makefile
|
||||
services/console/interface/irc_client/Makefile
|
||||
sugar/Makefile
|
||||
sugar/activity/Makefile
|
||||
sugar/clipboard/Makefile
|
||||
|
@ -30,7 +30,7 @@ CONSOLE_BUS = 'org.laptop.sugar.Console'
|
||||
CONSOLE_PATH = '/org/laptop/sugar/Console'
|
||||
CONSOLE_IFACE = 'org.laptop.sugar.Console'
|
||||
|
||||
class Console:
|
||||
class Console(object):
|
||||
|
||||
def __init__(self):
|
||||
# Main Window
|
||||
@ -54,6 +54,7 @@ class Console:
|
||||
self._load_interface('memphis', 'Memphis')
|
||||
self._load_interface('logviewer', 'Log Viewer')
|
||||
self._load_interface('terminal', 'Terminal')
|
||||
self._load_interface('irc_client', 'IRC Client')
|
||||
self._load_interface('ps_watcher', 'Presence')
|
||||
|
||||
main_hbox = gtk.HBox()
|
||||
@ -90,5 +91,4 @@ bus = dbus.SessionBus()
|
||||
name = dbus.service.BusName(CONSOLE_BUS, bus)
|
||||
|
||||
obj = Service(name)
|
||||
|
||||
gtk.main()
|
||||
|
@ -1,4 +1,4 @@
|
||||
SUBDIRS = memphis network logviewer terminal xo
|
||||
SUBDIRS = irc_client memphis network logviewer terminal xo
|
||||
|
||||
sugardir = $(pkgdatadir)/services/console/interface
|
||||
sugar_PYTHON = \
|
||||
|
5
services/console/interface/irc_client/Makefile.am
Normal file
5
services/console/interface/irc_client/Makefile.am
Normal file
@ -0,0 +1,5 @@
|
||||
sugardir = $(pkgdatadir)/services/console/interface/irc_client
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
irc_client.py
|
||||
|
2
services/console/interface/irc_client/__init__.py
Normal file
2
services/console/interface/irc_client/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from irc_client import Interface
|
||||
|
28
services/console/interface/irc_client/irc_client.py
Normal file
28
services/console/interface/irc_client/irc_client.py
Normal file
@ -0,0 +1,28 @@
|
||||
import gtk
|
||||
import purk
|
||||
|
||||
class IRCGui(gtk.VBox):
|
||||
_DEFAULT_SERVER = "irc.freenode.net"
|
||||
_AUTO_JOIN_CHANNEL = "#olpc-help"
|
||||
|
||||
def __init__(self):
|
||||
gtk.VBox.__init__(self, False)
|
||||
|
||||
connect_button = gtk.Button('Connect to OLPC Help Channel')
|
||||
connect_button.connect('clicked', self._on_connect_clicked_cb)
|
||||
|
||||
self._client = purk.Client()
|
||||
self._client.add_channel(self._AUTO_JOIN_CHANNEL)
|
||||
client_widget = self._client.get_widget()
|
||||
|
||||
self.pack_start(connect_button, False, False, 1)
|
||||
self.pack_start(client_widget)
|
||||
self.show_all()
|
||||
|
||||
def _on_connect_clicked_cb(self, widget):
|
||||
self._client.join_server(self._DEFAULT_SERVER)
|
||||
|
||||
class Interface(object):
|
||||
def __init__(self):
|
||||
self.widget = IRCGui()
|
||||
|
@ -188,6 +188,8 @@ class Interface:
|
||||
# Aditional files to watch in logviewer
|
||||
ext_files = []
|
||||
ext_files.append(xserver_logfile)
|
||||
ext_files.append("/var/log/syslog")
|
||||
ext_files.append("/var/log/messages")
|
||||
|
||||
viewer = MultiLogView(path, ext_files)
|
||||
self.widget = viewer.hbox
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
import gobject
|
||||
from net.device import Device
|
||||
from ui.treeview import TreeView
|
||||
from gui.treeview import TreeView
|
||||
|
||||
class NetworkView(TreeView):
|
||||
def __init__(self):
|
||||
|
@ -101,7 +101,6 @@ class XO_CPU(gtk.Frame):
|
||||
gobject.timeout_add(self._DRW_CPU.frequency, self._update_cpu_usage)
|
||||
|
||||
def _update_cpu_usage(self):
|
||||
print "update XO CPU"
|
||||
self._cpu = self._DRW_CPU._get_CPU_usage()
|
||||
self.set_label('System CPU Usage: ' + str(self._cpu) + '%')
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
SUBDIRS = procmem graphics net ui
|
||||
SUBDIRS = procmem graphics net gui purk
|
||||
|
||||
sugardir = $(pkgdatadir)/shell/console/lib
|
||||
sugar_PYTHON =
|
||||
|
5
services/console/lib/gui/Makefile.am
Normal file
5
services/console/lib/gui/Makefile.am
Normal file
@ -0,0 +1,5 @@
|
||||
sugardir = $(pkgdatadir)/services/console/lib/gui
|
||||
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
treeview.py
|
0
services/console/lib/gui/__init__.py
Normal file
0
services/console/lib/gui/__init__.py
Normal file
73
services/console/lib/gui/treeview.py
Normal file
73
services/console/lib/gui/treeview.py
Normal file
@ -0,0 +1,73 @@
|
||||
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
|
||||
import gtk
|
||||
|
||||
class TreeView(gtk.ScrolledWindow):
|
||||
iters = [] # Iters index
|
||||
|
||||
# Create a window with a treeview object
|
||||
#
|
||||
# cols = List of dicts, ex:
|
||||
#
|
||||
# cols = []
|
||||
# cols.append({'index': integer_index_position, 'name': string_col_name})
|
||||
def __init__(self, cols_def, cols_name):
|
||||
gtk.ScrolledWindow.__init__(self)
|
||||
|
||||
self._iters = []
|
||||
self._treeview = gtk.TreeView()
|
||||
|
||||
# Creating column data types
|
||||
self._store = gtk.TreeStore(*cols_def)
|
||||
|
||||
# Columns definition
|
||||
cell = gtk.CellRendererText()
|
||||
tv_cols = []
|
||||
|
||||
i=0
|
||||
for col in cols_name:
|
||||
col_tv = gtk.TreeViewColumn(col['name'], cell, text=i)
|
||||
col_tv.set_reorderable(True)
|
||||
col_tv.set_resizable(True)
|
||||
tv_cols.append(col_tv)
|
||||
i+=1
|
||||
|
||||
# Setting treeview properties
|
||||
self._treeview.set_model(self._store)
|
||||
self._treeview.set_enable_search(True)
|
||||
self._treeview.set_rules_hint(True)
|
||||
|
||||
for col in tv_cols:
|
||||
self._treeview.append_column(col)
|
||||
self.add(self._treeview)
|
||||
|
||||
def add_row(self, cols_data):
|
||||
iter = self._store.insert_after(None, None)
|
||||
for col in cols_data:
|
||||
print col['index'],col['info']
|
||||
self._store.set_value(iter, int(col['index']) , col['info'])
|
||||
|
||||
self.iters.append(iter)
|
||||
return iter
|
||||
|
||||
def update_row(self, iter, cols_data):
|
||||
for col in cols_data:
|
||||
self._store.set_value(iter, int(col['index']) , str(col['info']))
|
||||
|
||||
def remove_row(self, iter):
|
||||
self._store.remove(iter)
|
7
services/console/lib/purk/ABOUT
Normal file
7
services/console/lib/purk/ABOUT
Normal file
@ -0,0 +1,7 @@
|
||||
Urk is a PyGTK IRC Client written by Vincent Povirk and Marc Liddell.
|
||||
This current version has been modified in order to have an PyGTK
|
||||
IRC Client Widget called 'Purk'.
|
||||
|
||||
Suggestions are welcome...
|
||||
|
||||
Eduardo Silva <edsiper@gmail.com>
|
340
services/console/lib/purk/COPYING
Normal file
340
services/console/lib/purk/COPYING
Normal file
@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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.
|
||||
|
||||
<signature of Ty Coon>, 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.
|
17
services/console/lib/purk/Makefile.am
Normal file
17
services/console/lib/purk/Makefile.am
Normal file
@ -0,0 +1,17 @@
|
||||
SUBDIRS = scripts
|
||||
sugardir = $(pkgdatadir)/services/console/lib/purk
|
||||
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
conf.py \
|
||||
events.py \
|
||||
info.py \
|
||||
irc.py \
|
||||
parse_mirc.py \
|
||||
servers.py \
|
||||
ui.py \
|
||||
urk_trace.py \
|
||||
widgets.py \
|
||||
windows.py \
|
||||
README \
|
||||
ABOUT
|
186
services/console/lib/purk/README
Normal file
186
services/console/lib/purk/README
Normal file
@ -0,0 +1,186 @@
|
||||
urk 0.-1.cvs
|
||||
http://urk.sourceforge.net/
|
||||
|
||||
|
||||
Overview:
|
||||
|
||||
urk is an IRC client written purely in python for linux/gnome. It has a powerful
|
||||
built-in scripting system (also python), which is used to implement much of the
|
||||
client.
|
||||
|
||||
|
||||
Requirements/User installation:
|
||||
|
||||
urk requires the following packages (and should run on any os that can provide
|
||||
them):
|
||||
-python version 2.4 or greater (www.python.org)
|
||||
-pygtk 2.6 or greater (www.pygtk.org)
|
||||
|
||||
Most Linux (or at least GNOME) users should have these things already or be able
|
||||
to easily install them.
|
||||
|
||||
Because urk is pure python, no compilation of urk is required. Just extract the
|
||||
source to somewhere and run 'python urk.py'.
|
||||
|
||||
Windows versions of the above should theoretically work but may not in practice.
|
||||
I am hoping someone will come along and actually support urk on windows.
|
||||
|
||||
|
||||
Optional requirements:
|
||||
|
||||
urk can also make use of these packages if you have them:
|
||||
-pygtksourceview, part of gnome-python-extras <=2.12 and gnome-python-desktop
|
||||
>=2.14, for source highlighting and undo in the internal script editor
|
||||
-python-dbus, for, well, not much (if dbus is available, urk only makes a single
|
||||
instance per session, and commands can be executed remotely by calling urk.py)
|
||||
|
||||
|
||||
Getting started:
|
||||
|
||||
Make sure you have all the requirements, go to the directory where you have
|
||||
extracted the source, and type 'python urk.py'.
|
||||
|
||||
We don't have any preferences windows yet. You can change your nickname by
|
||||
typing '/nick nickname' (replacing nickname with the nick you want to use)
|
||||
or typing a new nick in the nick field on the lower right corner and pressing
|
||||
enter.
|
||||
|
||||
To connect, type '/server irc.gamesurge.net' (replacing irc.gamesurge.net with
|
||||
the server you want to connect to).
|
||||
|
||||
If you want to connect to another server (without disconnecting the current
|
||||
one), use the -m switch as in '/server -m irc.gamesurge.net'.
|
||||
|
||||
To join a channel when you're connected to a server, type '/join #channelname',
|
||||
replacing #channelname with the channel you want to join.
|
||||
|
||||
urk currently only supports the bare minimum commands and features you should
|
||||
need to connect and chat normally. On channels, you can send messages by
|
||||
typing them (if you want to send a message that starts with a /, use /say to
|
||||
send your message). You can send actions to channels with /me and send messages
|
||||
to arbitrary targets with /msg. If urk does not recognize a command, it will
|
||||
send it to the server. This works to implement most commands you would expect
|
||||
on an irc client.
|
||||
|
||||
|
||||
Configuration:
|
||||
|
||||
Most configuration has to be done manually. If urk is running, you can configure
|
||||
it using commands. The settings are stored in urk.conf on your profile
|
||||
directory, which you can locate by typing '/pyeval urk.userpath' in urk.
|
||||
|
||||
To set a value in urk, type
|
||||
|
||||
/pyexec conf.conf['setting'] = value
|
||||
|
||||
To see the current value, type
|
||||
|
||||
/pyeval conf.conf['setting']
|
||||
|
||||
To unset a value (meaning urk will use the default), type
|
||||
|
||||
/pyexec del conf.conf['setting']
|
||||
|
||||
Setting: Description:
|
||||
|
||||
'nick' The nickname urk should use. The default is to try to get
|
||||
it from the os. Put this in quotes when you set it.
|
||||
|
||||
'altnicks' A list of alternative nicknames to use. The default is
|
||||
an empty list.
|
||||
|
||||
'quitmsg' The message people see when you quit. The default is to
|
||||
advertise urk with your current version; we have to promote it somehow.
|
||||
This value needs to be in quotes.
|
||||
|
||||
'autoreconnect' If True, urk will try to reconnect when you're
|
||||
disconnected from a network. Defaults to True. Set this to True or False.
|
||||
|
||||
'highlight_words' A list of words, in addition to your nick, that cause a
|
||||
highlight event (normally the tab label turns blue and, if it's available,
|
||||
the tray icon shows up). For example: ['python', 'whale', 'peanut butter']
|
||||
|
||||
'log_dir' The place where logs are written. The default is a
|
||||
directory called "logs" on your profile directory.
|
||||
|
||||
'ui-gtk/tab-pos' The side of the window where the tabs will reside
|
||||
2 for top
|
||||
0 for left
|
||||
1 for right
|
||||
3 for bottom (default)
|
||||
|
||||
'ui-gtk/show-menubar' If True, the menubar is shown. The default is True. Set
|
||||
this to True or False.
|
||||
|
||||
'command_prefix' The prefix used to signal a command. Defaults to '/'
|
||||
|
||||
'font' The font used for output. Defaults to "sans 8".
|
||||
|
||||
'bg_color' The background color ("black" or "#000000").
|
||||
|
||||
'fg_color' The foreground color ("white" or "#ffffff").
|
||||
|
||||
'timestamp' A timestamp that will show up before messages. The
|
||||
default is no timestamp. A simple timestamp with hours and minutes is
|
||||
"[%H:%M] ". See http://docs.python.org/lib/module-time.html#l2h-1955 for
|
||||
a key to format this string.
|
||||
|
||||
'start-console' If True, urk will start up with a special console window
|
||||
that shows debugging output (normally sent to a terminal) and accepts
|
||||
python expressions. Defaults to False.
|
||||
|
||||
'status' If True, urk will be in status window mode. Each network
|
||||
will ALWAYS have a status window. When not in status window mode, networks
|
||||
only have a status window when there are no channel windows. Defaults to
|
||||
False.
|
||||
|
||||
'open-file-command' The command used to open files and url's with your
|
||||
preferred application (such as "gnome-open"). This is ignored on Windows,
|
||||
and you should never need to mess with this, ever.
|
||||
|
||||
|
||||
System-wide installation:
|
||||
|
||||
Not yet implemented.
|
||||
|
||||
|
||||
About scripting:
|
||||
|
||||
urk scripts are python source files that contain definitions of functions with
|
||||
certain "magic" names, like onText (for when someone sends a channel or private
|
||||
message). See www.python.org for help on writing python code. The format for
|
||||
onText is:
|
||||
|
||||
def onText(e):
|
||||
code
|
||||
|
||||
e is an object used to pass on the various information relating to the event
|
||||
that triggered the function. The name is a convention we use, much like self.
|
||||
|
||||
e.source is the nickname of the person who sent the message.
|
||||
|
||||
e.target is the nickname or channel that received the message.
|
||||
|
||||
e.text is the text of the message.
|
||||
|
||||
e.network is an object representing the network where the message was sent.
|
||||
|
||||
e.window is a window that seems to be related to the event for some unspecified
|
||||
reason. It could be the status window, a channel window, a query, anything.
|
||||
|
||||
Complete documentation doesn't exist yet. Sorry. Ask us or look at the source. theme.py is good for finding event names.
|
||||
|
||||
|
||||
Bugs/Feedback:
|
||||
|
||||
Naturally, feedback of any sort is welcome. Of course we want to know about
|
||||
bugs. In particular, we'd also like to hear about any features you want or
|
||||
expect in an irc client that urk doesn't have. While we'd like to limit the
|
||||
things that go in the default client (a notify list, for example, is something
|
||||
we'd want to see as an external script, not as part of the default setup, and
|
||||
something we're not likely to implement soon), there are probably a lot of
|
||||
little things that we may have skipped over because we don't use them or have
|
||||
become used to urk not having them.
|
||||
|
||||
The best way to get in touch with us is by irc, at #urk on irc.gamesurge.net.
|
||||
Or send a message to the mailing list, urk-discussion@lists.sourceforge.net.
|
245
services/console/lib/purk/UrkLogQueryable.cs
Normal file
245
services/console/lib/purk/UrkLogQueryable.cs
Normal file
@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using Beagle.Daemon;
|
||||
using Beagle.Util;
|
||||
|
||||
namespace Beagle.Daemon.UrkLogQueryable {
|
||||
|
||||
[QueryableFlavor (Name="UrkLog", Domain=QueryDomain.Local, RequireInotify=false)]
|
||||
public class UrkLogQueryable : LuceneFileQueryable {
|
||||
|
||||
private static Logger log = Logger.Get ("UrkLogQueryable");
|
||||
|
||||
private string config_dir, log_dir, icons_dir;
|
||||
|
||||
private int polling_interval_in_seconds = 60;
|
||||
|
||||
//private GaimBuddyListReader list = new GaimBuddyListReader ();
|
||||
|
||||
public UrkLogQueryable () : base ("UrkLogIndex")
|
||||
{
|
||||
config_dir = Path.Combine (PathFinder.HomeDir, ".urk");
|
||||
log_dir = Path.Combine (config_dir, "logs");
|
||||
icons_dir = Path.Combine (config_dir, "icons");
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
private void StartWorker()
|
||||
{
|
||||
if (! Directory.Exists (log_dir)) {
|
||||
GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForExistence));
|
||||
return;
|
||||
}
|
||||
|
||||
log.Info ("Starting urk log backend");
|
||||
|
||||
Stopwatch stopwatch = new Stopwatch ();
|
||||
stopwatch.Start ();
|
||||
|
||||
State = QueryableState.Crawling;
|
||||
Crawl ();
|
||||
State = QueryableState.Idle;
|
||||
|
||||
if (!Inotify.Enabled) {
|
||||
Scheduler.Task task = Scheduler.TaskFromHook (new Scheduler.TaskHook (CrawlHook));
|
||||
task.Tag = "Crawling ~/.urk/logs to find new logfiles";
|
||||
task.Source = this;
|
||||
ThisScheduler.Add (task);
|
||||
}
|
||||
|
||||
stopwatch.Stop ();
|
||||
|
||||
log.Info ("urk log backend worker thread done in {0}", stopwatch);
|
||||
}
|
||||
|
||||
public override void Start ()
|
||||
{
|
||||
base.Start ();
|
||||
|
||||
ExceptionHandlingThread.Start (new ThreadStart (StartWorker));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
private void CrawlHook (Scheduler.Task task)
|
||||
{
|
||||
Crawl ();
|
||||
task.Reschedule = true;
|
||||
task.TriggerTime = DateTime.Now.AddSeconds (polling_interval_in_seconds);
|
||||
}
|
||||
|
||||
private void Crawl ()
|
||||
{
|
||||
Inotify.Subscribe (log_dir, OnInotifyNewProtocol, Inotify.EventType.Create);
|
||||
|
||||
// Walk through protocol subdirs
|
||||
foreach (string proto_dir in DirectoryWalker.GetDirectories (log_dir))
|
||||
CrawlProtocolDirectory (proto_dir);
|
||||
}
|
||||
|
||||
private void CrawlNetworkDirectory (string proto_dir)
|
||||
{
|
||||
Inotify.Subscribe (proto_dir, OnInotifyNewTarget, Inotify.EventType.Create);
|
||||
|
||||
// Walk through accounts
|
||||
foreach (string account_dir in DirectoryWalker.GetDirectories (proto_dir))
|
||||
CrawlTargetDirectory (account_dir);
|
||||
}
|
||||
|
||||
private void CrawlTargetDirectory (string account_dir)
|
||||
{
|
||||
Inotify.Subscribe (account_dir, OnInotifyNewRemote, Inotify.EventType.Create);
|
||||
|
||||
// Walk through remote user conversations
|
||||
foreach (string remote_dir in DirectoryWalker.GetDirectories (account_dir))
|
||||
CrawlRemoteDirectory (remote_dir);
|
||||
}
|
||||
|
||||
private void CrawlRemoteDirectory (string remote_dir)
|
||||
{
|
||||
Inotify.Subscribe (remote_dir, OnInotifyNewConversation, Inotify.EventType.CloseWrite);
|
||||
|
||||
foreach (FileInfo file in DirectoryWalker.GetFileInfos (remote_dir))
|
||||
if (FileIsInteresting (file.Name))
|
||||
IndexLog (file.FullName, Scheduler.Priority.Delayed);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
private bool CheckForExistence ()
|
||||
{
|
||||
if (!Directory.Exists (log_dir))
|
||||
return true;
|
||||
|
||||
this.Start ();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool FileIsInteresting (string filename)
|
||||
{
|
||||
// Filename must be fixed length, see below
|
||||
if (filename.Length < 21 || filename.Length > 22)
|
||||
return false;
|
||||
|
||||
// Check match on regex: ^[0-9]{4}-[0-9]{2}-[0-9]{2}\\.[0-9]{6}\\.(txt|html)$
|
||||
// e.g. 2005-07-22.161521.txt
|
||||
// We'd use System.Text.RegularExpressions if they werent so much more expensive
|
||||
return Char.IsDigit (filename [0]) && Char.IsDigit (filename [1])
|
||||
&& Char.IsDigit (filename [2]) && Char.IsDigit (filename [3])
|
||||
&& filename [4] == '-'
|
||||
&& Char.IsDigit (filename [5]) && Char.IsDigit (filename [6])
|
||||
&& filename [7] == '-'
|
||||
&& Char.IsDigit (filename [8]) && Char.IsDigit (filename [9])
|
||||
&& filename [10] == '.'
|
||||
&& Char.IsDigit (filename [11]) && Char.IsDigit (filename [12])
|
||||
&& Char.IsDigit (filename [13]) && Char.IsDigit (filename [14])
|
||||
&& Char.IsDigit (filename [15]) && Char.IsDigit (filename [16])
|
||||
&& filename [17] == '.'
|
||||
&& ( (filename [18] == 't' && filename [19] == 'x' && filename [20] == 't')
|
||||
|| (filename [18] == 'h' && filename [19] == 't' && filename [20] == 'm' && filename [21] == 'l')
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
private void OnInotifyNewNetwork (Inotify.Watch watch,
|
||||
string path, string subitem, string srcpath,
|
||||
Inotify.EventType type)
|
||||
{
|
||||
if (subitem.Length == 0 || (type & Inotify.EventType.IsDirectory) == 0)
|
||||
return;
|
||||
|
||||
CrawlNetworkDirectory (Path.Combine (path, subitem));
|
||||
}
|
||||
|
||||
private void OnInotifyNewTarget (Inotify.Watch watch,
|
||||
string path, string subitem, string srcpath,
|
||||
Inotify.EventType type)
|
||||
{
|
||||
if (subitem.Length == 0 || (type & Inotify.EventType.IsDirectory) == 0)
|
||||
return;
|
||||
|
||||
CrawlTargetDirectory (Path.Combine (path, subitem));
|
||||
}
|
||||
|
||||
private void OnInotifyNewRemote (Inotify.Watch watch,
|
||||
string path, string subitem, string srcpath,
|
||||
Inotify.EventType type)
|
||||
{
|
||||
if (subitem.Length == 0 || (type & Inotify.EventType.IsDirectory) == 0)
|
||||
return;
|
||||
|
||||
CrawlRemoteDirectory (Path.Combine (path, subitem));
|
||||
}
|
||||
|
||||
private void OnInotifyNewConversation (Inotify.Watch watch,
|
||||
string path, string subitem, string srcpath,
|
||||
Inotify.EventType type)
|
||||
{
|
||||
if (subitem.Length == 0 || (type & Inotify.EventType.IsDirectory) != 0)
|
||||
return;
|
||||
|
||||
if (FileIsInteresting (subitem))
|
||||
IndexLog (Path.Combine (path, subitem), Scheduler.Priority.Immediate);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
private static Indexable IRCLogToIndexable (string filename)
|
||||
{
|
||||
Uri uri = UriFu.PathToFileUri (filename);
|
||||
Indexable indexable = new Indexable (uri);
|
||||
indexable.ContentUri = uri;
|
||||
indexable.Timestamp = File.GetLastWriteTimeUtc (filename);
|
||||
indexable.MimeType = GaimLog.MimeType; // XXX
|
||||
indexable.HitType = "IRCLog";
|
||||
indexable.CacheContent = false;
|
||||
|
||||
return indexable;
|
||||
}
|
||||
|
||||
private void IndexLog (string filename, Scheduler.Priority priority)
|
||||
{
|
||||
if (! File.Exists (filename))
|
||||
return;
|
||||
|
||||
if (IsUpToDate (filename))
|
||||
return;
|
||||
|
||||
Indexable indexable = IRCLogToIndexable (filename);
|
||||
Scheduler.Task task = NewAddTask (indexable);
|
||||
task.Priority = priority;
|
||||
task.SubPriority = 0;
|
||||
ThisScheduler.Add (task);
|
||||
}
|
||||
|
||||
override protected double RelevancyMultiplier (Hit hit)
|
||||
{
|
||||
return HalfLifeMultiplierFromProperty (hit, 0.25,
|
||||
"fixme:endtime", "fixme:starttime");
|
||||
}
|
||||
|
||||
override protected bool HitFilter (Hit hit)
|
||||
{
|
||||
/*ImBuddy buddy = list.Search (hit ["fixme:speakingto"]);
|
||||
|
||||
if (buddy != null) {
|
||||
if (buddy.Alias != "")
|
||||
hit ["fixme:speakingto_alias"] = buddy.Alias;
|
||||
|
||||
//if (buddy.BuddyIconLocation != "")
|
||||
// hit ["fixme:speakingto_icon"] = Path.Combine (icons_dir, buddy.BuddyIconLocation);
|
||||
}*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
101
services/console/lib/purk/__init__.py
Normal file
101
services/console/lib/purk/__init__.py
Normal file
@ -0,0 +1,101 @@
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import events
|
||||
import windows
|
||||
|
||||
urkpath = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
if os.path.abspath(os.curdir) != os.path.join(urkpath):
|
||||
sys.path[0] = os.path.join(urkpath)
|
||||
|
||||
sys.path = [
|
||||
os.path.join(urkpath, "scripts"),
|
||||
] + sys.path
|
||||
|
||||
script_path = urkpath+"/scripts"
|
||||
|
||||
from ui import *
|
||||
|
||||
# Here I'm trying to handle the original URL IRC Client, urk don't use
|
||||
# normal classes . Let's try to get a urk widget:
|
||||
class Trigger(object):
|
||||
def __init__(self):
|
||||
self._mods = []
|
||||
self.events = events
|
||||
self._load_scripts()
|
||||
|
||||
def _load_scripts(self):
|
||||
script_path = urkpath + "/scripts"
|
||||
try:
|
||||
suffix = os.extsep+"py"
|
||||
for script in os.listdir(script_path):
|
||||
if script.endswith(suffix):
|
||||
try:
|
||||
mod = self.events.load(script)
|
||||
self._mods.append(mod)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
print "Failed loading script %s." % script
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def get_modules(self):
|
||||
return self._mods
|
||||
|
||||
class Core(object):
|
||||
def __init__(self):
|
||||
self.window = None
|
||||
self.trigger = Trigger()
|
||||
self.events = self.trigger.events
|
||||
self.manager = widgets.UrkUITabs(self)
|
||||
self.channels = []
|
||||
|
||||
mods = self.trigger.get_modules()
|
||||
for m in mods:
|
||||
m.core = self
|
||||
m.manager = self.manager
|
||||
|
||||
if not self.window:
|
||||
self.window = windows.new(windows.StatusWindow, irc.Network(self), "status", self)
|
||||
self.window.activate()
|
||||
|
||||
register_idle(self.trigger_start)
|
||||
gtk.gdk.threads_enter()
|
||||
|
||||
def run_command(self, command):
|
||||
offset = 0
|
||||
if command[0] == '/':
|
||||
offset = 1
|
||||
|
||||
self.events.run(command[offset:], self.manager.get_active(), self.window.network)
|
||||
|
||||
def trigger_start(self):
|
||||
self.events.trigger("Start")
|
||||
|
||||
def _add_script(self, module):
|
||||
return
|
||||
|
||||
class Client(object):
|
||||
def __init__(self):
|
||||
self.core = Core()
|
||||
self.widget = self.core.manager.box
|
||||
|
||||
def run_command(self, command):
|
||||
self.core.run_command(command)
|
||||
|
||||
def join_server(self, network_name, port=6667):
|
||||
command = 'server '+ network_name + ' ' + str(port)
|
||||
self.run_command(command)
|
||||
|
||||
def get_widget(self):
|
||||
return self.widget
|
||||
|
||||
def show(self):
|
||||
self.widget.show_all()
|
||||
|
||||
def add_channel(self, channel):
|
||||
self.core.channels.append(channel)
|
||||
|
||||
def clear_channels(self):
|
||||
self.core.channels = []
|
86
services/console/lib/purk/conf.py
Normal file
86
services/console/lib/purk/conf.py
Normal file
@ -0,0 +1,86 @@
|
||||
import os
|
||||
urkpath = os.path.dirname(__file__)
|
||||
|
||||
def path(filename=""):
|
||||
if filename:
|
||||
return os.path.join(urkpath, filename)
|
||||
else:
|
||||
return urkpath
|
||||
|
||||
if os.access(path('profile'),os.F_OK) or os.path.expanduser("~") == "~":
|
||||
userpath = path('profile')
|
||||
if not os.access(userpath,os.F_OK):
|
||||
os.mkdir(userpath)
|
||||
if not os.access(os.path.join(userpath,'scripts'),os.F_OK):
|
||||
os.mkdir(os.path.join(userpath,'scripts'))
|
||||
else:
|
||||
userpath = os.path.join(os.path.expanduser("~"), ".urk")
|
||||
if not os.access(userpath,os.F_OK):
|
||||
os.mkdir(userpath, 0700)
|
||||
if not os.access(os.path.join(userpath,'scripts'),os.F_OK):
|
||||
os.mkdir(os.path.join(userpath,'scripts'), 0700)
|
||||
|
||||
CONF_FILE = os.path.join(userpath,'urk.conf')
|
||||
|
||||
|
||||
def pprint(obj, depth=-2):
|
||||
depth += 2
|
||||
|
||||
string = []
|
||||
|
||||
if isinstance(obj, dict):
|
||||
if obj:
|
||||
string.append('{\n')
|
||||
|
||||
for key in obj:
|
||||
string.append('%s%s%s' % (' '*depth, repr(key), ': '))
|
||||
string += pprint(obj[key], depth)
|
||||
|
||||
string.append('%s%s' % (' '*depth, '},\n'))
|
||||
|
||||
else:
|
||||
string.append('{},\n')
|
||||
|
||||
elif isinstance(obj, list):
|
||||
if obj:
|
||||
string.append('[\n')
|
||||
|
||||
for item in obj:
|
||||
string.append('%s' % (' '*depth))
|
||||
string += pprint(item, depth)
|
||||
|
||||
string.append('%s%s' % (' '*depth, '],\n'))
|
||||
|
||||
else:
|
||||
string.append('[],\n')
|
||||
|
||||
else:
|
||||
string.append('%s,\n' % (repr(obj),))
|
||||
|
||||
if depth:
|
||||
return string
|
||||
else:
|
||||
return ''.join(string)[:-2]
|
||||
|
||||
def save(*args):
|
||||
new_file = not os.access(CONF_FILE,os.F_OK)
|
||||
fd = file(CONF_FILE, "wb")
|
||||
try:
|
||||
if new_file:
|
||||
os.chmod(CONF_FILE,0600)
|
||||
fd.write(pprint(conf))
|
||||
finally:
|
||||
fd.close()
|
||||
|
||||
#events.register('Exit', 'post', save)
|
||||
|
||||
try:
|
||||
conf = eval(file(CONF_FILE, "U").read().strip())
|
||||
except IOError, e:
|
||||
if e.args[0] == 2:
|
||||
conf = {}
|
||||
else:
|
||||
raise
|
||||
|
||||
if __name__ == '__main__':
|
||||
print pprint(conf)
|
292
services/console/lib/purk/events.py
Normal file
292
services/console/lib/purk/events.py
Normal file
@ -0,0 +1,292 @@
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
|
||||
pyending = os.extsep + 'py'
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
class EventStopError(error):
|
||||
pass
|
||||
|
||||
class CommandError(error):
|
||||
pass
|
||||
|
||||
class data(object):
|
||||
done = False
|
||||
quiet = False
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for attr in kwargs.items():
|
||||
setattr(self, *attr)
|
||||
|
||||
trigger_sequence = ("pre", "setup", "on", "setdown", "post")
|
||||
|
||||
all_events = {}
|
||||
loaded = {}
|
||||
|
||||
# An event has occurred, the e_name event!
|
||||
def trigger(e_name, e_data=None, **kwargs):
|
||||
if e_data is None:
|
||||
e_data = data(**kwargs)
|
||||
|
||||
#print 'Event:', e_name, e_data
|
||||
|
||||
failure = True
|
||||
error = None
|
||||
|
||||
if e_name in all_events:
|
||||
for e_stage in trigger_sequence:
|
||||
if e_stage in all_events[e_name]:
|
||||
for f_ref, s_name in all_events[e_name][e_stage]:
|
||||
try:
|
||||
f_ref(e_data)
|
||||
except EventStopError:
|
||||
return
|
||||
except CommandError, e:
|
||||
error = e.args
|
||||
continue
|
||||
except:
|
||||
traceback.print_exc()
|
||||
failure = False
|
||||
if failure:
|
||||
print "Error handling: " + e_name
|
||||
|
||||
return error
|
||||
|
||||
# Stop all processing of the current event now!
|
||||
def halt():
|
||||
raise EventStopError
|
||||
|
||||
# Registers a specific function with an event at the given sequence stage.
|
||||
def register(e_name, e_stage, f_ref, s_name=""):
|
||||
global all_events
|
||||
|
||||
if e_name not in all_events:
|
||||
all_events[e_name] = {}
|
||||
|
||||
if e_stage not in all_events[e_name]:
|
||||
all_events[e_name][e_stage] = []
|
||||
|
||||
all_events[e_name][e_stage] += [(f_ref, s_name)]
|
||||
|
||||
# turn a filename (or module name) and trim it to the name of the module
|
||||
def get_scriptname(name):
|
||||
s_name = os.path.basename(name)
|
||||
if s_name.endswith(pyending):
|
||||
s_name = s_name[:-len(pyending)]
|
||||
return s_name
|
||||
|
||||
#take a given script name and turn it into a filename
|
||||
def get_filename(name):
|
||||
# split the directory and filename
|
||||
dirname = os.path.dirname(name)
|
||||
s_name = get_scriptname(name)
|
||||
|
||||
for path in dirname and (dirname,) or sys.path:
|
||||
filename = os.path.join(path, s_name + pyending)
|
||||
if os.access(filename, os.R_OK):
|
||||
return filename
|
||||
|
||||
raise ImportError("No urk script %s found" % name)
|
||||
|
||||
# register the events defined by obj
|
||||
def register_all(name, obj):
|
||||
# we look through everything defined in the file
|
||||
for f in dir(obj):
|
||||
# for each bit of the event sequence
|
||||
for e_stage in trigger_sequence:
|
||||
|
||||
# if the function is for this bit
|
||||
if f.startswith(e_stage):
|
||||
# get a reference to a function
|
||||
f_ref = getattr(obj, f)
|
||||
#print f
|
||||
# normalise to the event name
|
||||
e_name = f.replace(e_stage, "", 1)
|
||||
|
||||
# add our function to the right event section
|
||||
register(e_name, e_stage, f_ref, name)
|
||||
|
||||
break
|
||||
|
||||
#load a .py file into a new module object without affecting sys.modules
|
||||
def load_pyfile(filename):
|
||||
s_name = get_scriptname(filename)
|
||||
return __import__(s_name)
|
||||
|
||||
# Load a python script and register
|
||||
# the functions defined in it for events.
|
||||
# Return True if we loaded the script, False if it was already loaded
|
||||
def load(name):
|
||||
s_name = get_scriptname(name)
|
||||
filename = get_filename(name)
|
||||
|
||||
if s_name in loaded:
|
||||
return False
|
||||
|
||||
loaded[s_name] = None
|
||||
|
||||
try:
|
||||
module = load_pyfile(filename)
|
||||
loaded[s_name] = module
|
||||
except:
|
||||
del loaded[s_name]
|
||||
raise
|
||||
|
||||
register_all(s_name, loaded[s_name])
|
||||
|
||||
return module
|
||||
|
||||
# Is the script with the given name loaded?
|
||||
def is_loaded(name):
|
||||
return get_scriptname(name) in loaded
|
||||
|
||||
# Remove any function which was defined in the given script
|
||||
def unload(name):
|
||||
s_name = get_scriptname(name)
|
||||
|
||||
del loaded[s_name]
|
||||
|
||||
for e_name in list(all_events):
|
||||
for e_stage in list(all_events[e_name]):
|
||||
to_check = all_events[e_name][e_stage]
|
||||
|
||||
all_events[e_name][e_stage] = [(f, m) for f, m in to_check if m != s_name]
|
||||
|
||||
if not all_events[e_name][e_stage]:
|
||||
del all_events[e_name][e_stage]
|
||||
|
||||
if not all_events[e_name]:
|
||||
del all_events[e_name]
|
||||
|
||||
def reload(name):
|
||||
s_name = get_scriptname(name)
|
||||
|
||||
if s_name not in loaded:
|
||||
return False
|
||||
|
||||
temp = loaded[s_name]
|
||||
|
||||
unload(s_name)
|
||||
|
||||
try:
|
||||
load(name)
|
||||
return True
|
||||
except:
|
||||
loaded[s_name] = temp
|
||||
register_all(s_name, temp)
|
||||
raise
|
||||
|
||||
def run(text, window, network):
|
||||
split = text.split(' ')
|
||||
|
||||
c_data = data(name=split.pop(0), text=text, window=window, network=network)
|
||||
|
||||
if split and split[0].startswith('-'):
|
||||
c_data.switches = set(split.pop(0)[1:])
|
||||
else:
|
||||
c_data.switches = set()
|
||||
|
||||
c_data.args = split
|
||||
|
||||
event_name = "Command" + c_data.name.capitalize()
|
||||
|
||||
if event_name in all_events:
|
||||
result = trigger(event_name, c_data)
|
||||
|
||||
if result:
|
||||
c_data.window.write("* /%s: %s" % (c_data.name, result[0]))
|
||||
else:
|
||||
trigger("Command", c_data)
|
||||
|
||||
if not c_data.done:
|
||||
c_data.window.write("* /%s: No such command exists" % (c_data.name))
|
||||
|
||||
def onCommandPyeval(e):
|
||||
loc = sys.modules.copy()
|
||||
loc.update(e.__dict__)
|
||||
import pydoc #fix nonresponsive help() command
|
||||
old_pager, pydoc.pager = pydoc.pager, pydoc.plainpager
|
||||
try:
|
||||
result = repr(eval(' '.join(e.args), loc))
|
||||
if 's' in e.switches:
|
||||
run(
|
||||
'say - %s => %s' % (' '.join(e.args),result),
|
||||
e.window,
|
||||
e.network
|
||||
)
|
||||
else:
|
||||
e.window.write(result)
|
||||
except:
|
||||
for line in traceback.format_exc().split('\n'):
|
||||
e.window.write(line)
|
||||
pydoc.pager = old_pager
|
||||
|
||||
def onCommandPyexec(e):
|
||||
loc = sys.modules.copy()
|
||||
loc.update(e.__dict__)
|
||||
import pydoc #fix nonresponsive help() command
|
||||
old_pager, pydoc.pager = pydoc.pager, pydoc.plainpager
|
||||
try:
|
||||
exec ' '.join(e.args) in loc
|
||||
except:
|
||||
for line in traceback.format_exc().split('\n'):
|
||||
e.window.write(line)
|
||||
pydoc.pager = old_pager
|
||||
|
||||
def onCommandLoad(e):
|
||||
if e.args:
|
||||
name = e.args[0]
|
||||
else:
|
||||
e.window.write('Usage: /load scriptname')
|
||||
|
||||
try:
|
||||
if load(name):
|
||||
e.window.write("* The script '%s' has been loaded." % name)
|
||||
else:
|
||||
raise CommandError("The script is already loaded; use /reload instead")
|
||||
except:
|
||||
e.window.write(traceback.format_exc(), line_ending='')
|
||||
raise CommandError("Error loading the script")
|
||||
|
||||
def onCommandUnload(e):
|
||||
if e.args:
|
||||
name = e.args[0]
|
||||
else:
|
||||
e.window.write('Usage: /unload scriptname')
|
||||
|
||||
if is_loaded(name):
|
||||
unload(name)
|
||||
e.window.write("* The script '%s' has been unloaded." % name)
|
||||
else:
|
||||
raise CommandError("No such script is loaded")
|
||||
|
||||
def onCommandReload(e):
|
||||
if e.args:
|
||||
name = e.args[0]
|
||||
else:
|
||||
e.window.write('Usage: /reload scriptname')
|
||||
|
||||
try:
|
||||
if reload(name):
|
||||
e.window.write("* The script '%s' has been reloaded." % name)
|
||||
else:
|
||||
raise CommandError("The script isn't loaded yet; use /load instead")
|
||||
except:
|
||||
e.window.write(traceback.format_exc(), line_ending='')
|
||||
|
||||
def onCommandScripts(e):
|
||||
e.window.write("Loaded scripts:")
|
||||
for name in loaded:
|
||||
e.window.write("* %s" % name)
|
||||
|
||||
def onCommandEcho(e):
|
||||
e.window.write(' '.join(e.args))
|
||||
|
||||
name = ''
|
||||
for name in globals():
|
||||
if name.startswith('onCommand'):
|
||||
register(name[2:], "on", globals()[name], '_all_events')
|
||||
del name
|
8
services/console/lib/purk/info.py
Normal file
8
services/console/lib/purk/info.py
Normal file
@ -0,0 +1,8 @@
|
||||
name = "PUrk"
|
||||
long_name = "Purk IRC"
|
||||
version = 0, 1, "git"
|
||||
long_version = "%s v%s" % (long_name, ".".join(str(x) for x in version))
|
||||
website = "http://dev.laptop.org/"
|
||||
authors = ["Vincent Povirk", "Marc Liddell"]
|
||||
copyright = "2005 %s" % ', '.join(authors)
|
||||
|
333
services/console/lib/purk/irc.py
Normal file
333
services/console/lib/purk/irc.py
Normal file
@ -0,0 +1,333 @@
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from conf import conf
|
||||
import ui
|
||||
import windows
|
||||
import info
|
||||
|
||||
DISCONNECTED = 0
|
||||
CONNECTING = 1
|
||||
INITIALIZING = 2
|
||||
CONNECTED = 3
|
||||
|
||||
def parse_irc(msg, server):
|
||||
msg = msg.split(' ')
|
||||
|
||||
# if our very first character is :
|
||||
# then this is the source,
|
||||
# otherwise insert the server as the source
|
||||
if msg and msg[0].startswith(':'):
|
||||
msg[0] = msg[0][1:]
|
||||
else:
|
||||
msg.insert(0, server)
|
||||
|
||||
# loop through the msg until we find
|
||||
# something beginning with :
|
||||
for i, token in enumerate(msg):
|
||||
if token.startswith(':'):
|
||||
# remove the :
|
||||
msg[i] = msg[i][1:]
|
||||
|
||||
# join up the rest
|
||||
msg[i:] = [' '.join(msg[i:])]
|
||||
break
|
||||
|
||||
# filter out the empty pre-":" tokens and add on the text to the end
|
||||
return [m for m in msg[:-1] if m] + msg[-1:]
|
||||
|
||||
# note: this sucks and makes very little sense, but it matches the BNF
|
||||
# as far as we've tested, which seems to be the goal
|
||||
|
||||
def default_nicks():
|
||||
try:
|
||||
nicks = [conf.get('nick')] + conf.get('altnicks',[])
|
||||
if not nicks[0]:
|
||||
import getpass
|
||||
nicks = [getpass.getuser()]
|
||||
except:
|
||||
nicks = ["mrurk"]
|
||||
return nicks
|
||||
|
||||
class Network(object):
|
||||
socket = None
|
||||
|
||||
def __init__(self, core, server="irc.default.org", port=6667, nicks=[],
|
||||
username="", fullname="", name=None, **kwargs):
|
||||
self.core = core
|
||||
self.manager = core.manager
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.events = core.events
|
||||
|
||||
self.name = name or server
|
||||
|
||||
self.nicks = nicks or default_nicks()
|
||||
self.me = self.nicks[0]
|
||||
|
||||
self.username = username or "urk"
|
||||
self.fullname = fullname or conf.get("fullname", self.username)
|
||||
self.password = ''
|
||||
|
||||
self.isupport = {
|
||||
'NETWORK': server,
|
||||
'PREFIX': '(ohv)@%+',
|
||||
'CHANMODES': 'b,k,l,imnpstr',
|
||||
}
|
||||
self.prefixes = {'o':'@', 'h':'%', 'v':'+', '@':'o', '%':'h', '+':'v'}
|
||||
|
||||
self.status = DISCONNECTED
|
||||
self.failedhosts = [] #hosts we've tried and failed to connect to
|
||||
self.channel_prefixes = '&#+$' # from rfc2812
|
||||
|
||||
self.on_channels = set()
|
||||
self.requested_joins = set()
|
||||
self.requested_parts = set()
|
||||
|
||||
self.buffer = ''
|
||||
|
||||
#called when we get a result from the dns lookup
|
||||
def on_dns(self, result, error):
|
||||
if error:
|
||||
self.disconnect(error=error[1])
|
||||
else:
|
||||
#import os
|
||||
#import random
|
||||
#random.seed()
|
||||
#random.shuffle(result)
|
||||
if socket.has_ipv6: #prefer ipv6
|
||||
result = [(f, t, p, c, a) for (f, t, p, c, a) in result if f == socket.AF_INET6]+result
|
||||
elif hasattr(socket,"AF_INET6"): #ignore ipv6
|
||||
result = [(f, t, p, c, a) for (f, t, p, c, a) in result if f != socket.AF_INET6]
|
||||
|
||||
self.failedlasthost = False
|
||||
|
||||
for f, t, p, c, a in result:
|
||||
if (f, t, p, c, a) not in self.failedhosts:
|
||||
try:
|
||||
self.socket = socket.socket(f, t, p)
|
||||
except:
|
||||
continue
|
||||
self.source = ui.fork(self.on_connect, self.socket.connect, a)
|
||||
self.failedhosts.append((f, t, p, c, a))
|
||||
if set(self.failedhosts) >= set(result):
|
||||
self.failedlasthost = True
|
||||
break
|
||||
else:
|
||||
self.failedlasthost = True
|
||||
if len(result):
|
||||
self.failedhosts[:] = (f, t, p, c, a),
|
||||
f, t, p, c, a = result[0]
|
||||
try:
|
||||
self.socket = socket.socket(f, t, p)
|
||||
self.source = ui.fork(self.on_connect, self.socket.connect, a)
|
||||
except:
|
||||
self.disconnect(error="Couldn't find a host we can connect to")
|
||||
else:
|
||||
self.disconnect(error="Couldn't find a host we can connect to")
|
||||
|
||||
#called when socket.open() returns
|
||||
def on_connect(self, result, error):
|
||||
if error:
|
||||
self.disconnect(error=error[1])
|
||||
#we should immediately retry if we failed to open the socket and there are hosts left
|
||||
if self.status == DISCONNECTED and not self.failedlasthost:
|
||||
windows.get_default(self, self.core.manager).write("* Retrying with next available host")
|
||||
self.connect()
|
||||
else:
|
||||
self.source = source = ui.Source()
|
||||
self.status = INITIALIZING
|
||||
self.failedhosts[:] = ()
|
||||
|
||||
self.events.trigger('SocketConnect', network=self)
|
||||
|
||||
if source.enabled:
|
||||
self.source = ui.fork(self.on_read, self.socket.recv, 8192)
|
||||
|
||||
# Auto join channels on connect
|
||||
for channel in self.core.channels:
|
||||
self.core.run_command("/join %s" % channel)
|
||||
|
||||
#called when we read data or failed to read data
|
||||
def on_read(self, result, error):
|
||||
if error:
|
||||
self.disconnect(error=error[1])
|
||||
elif not result:
|
||||
self.disconnect(error="Connection closed by remote host")
|
||||
else:
|
||||
self.source = source = ui.Source()
|
||||
|
||||
self.buffer = (self.buffer + result).split("\r\n")
|
||||
|
||||
for line in self.buffer[:-1]:
|
||||
self.got_msg(line)
|
||||
|
||||
if self.buffer:
|
||||
self.buffer = self.buffer[-1]
|
||||
else:
|
||||
self.buffer = ''
|
||||
|
||||
if source.enabled:
|
||||
self.source = ui.fork(self.on_read, self.socket.recv, 8192)
|
||||
|
||||
def raw(self, msg):
|
||||
self.events.trigger("OwnRaw", network=self, raw=msg)
|
||||
|
||||
if self.status >= INITIALIZING:
|
||||
self.socket.send(msg + "\r\n")
|
||||
|
||||
def got_msg(self, msg):
|
||||
pmsg = parse_irc(msg, self.server)
|
||||
|
||||
e_data = self.events.data(
|
||||
raw=msg,
|
||||
msg=pmsg,
|
||||
text=pmsg[-1],
|
||||
network=self,
|
||||
window=windows.get_default(self, self.manager)
|
||||
)
|
||||
|
||||
if "!" in pmsg[0]:
|
||||
e_data.source, e_data.address = pmsg[0].split('!',1)
|
||||
|
||||
else:
|
||||
e_data.source, e_data.address = pmsg[0], ''
|
||||
|
||||
if len(pmsg) > 2:
|
||||
e_data.target = pmsg[2]
|
||||
else:
|
||||
e_data.target = pmsg[-1]
|
||||
|
||||
self.events.trigger('Raw', e_data)
|
||||
|
||||
def connect(self):
|
||||
if not self.status:
|
||||
self.status = CONNECTING
|
||||
|
||||
self.source = ui.fork(self.on_dns, socket.getaddrinfo, self.server, self.port, 0, socket.SOCK_STREAM)
|
||||
|
||||
self.events.trigger('Connecting', network=self)
|
||||
|
||||
def disconnect(self, error=None):
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
|
||||
if self.source:
|
||||
self.source.unregister()
|
||||
self.source = None
|
||||
|
||||
self.socket = None
|
||||
|
||||
self.status = DISCONNECTED
|
||||
|
||||
#note: connecting from onDisconnect is probably a Bad Thing
|
||||
self.events.trigger('Disconnect', network=self, error=error)
|
||||
|
||||
#trigger a nick change if the nick we want is different from the one we
|
||||
# had.
|
||||
if self.me != self.nicks[0]:
|
||||
self.events.trigger(
|
||||
'Nick', network=self, window=windows.get_default(self),
|
||||
source=self.me, target=self.nicks[0], address='',
|
||||
text=self.nicks[0]
|
||||
)
|
||||
self.me = self.nicks[0]
|
||||
|
||||
def norm_case(self, string):
|
||||
return string.lower()
|
||||
|
||||
def quit(self, msg=None):
|
||||
if self.status:
|
||||
try:
|
||||
if msg == None:
|
||||
msg = conf.get('quitmsg', "%s - %s" % (info.long_version, info.website))
|
||||
self.raw("QUIT :%s" % msg)
|
||||
except:
|
||||
pass
|
||||
self.disconnect()
|
||||
|
||||
def join(self, target, key='', requested=True):
|
||||
if key:
|
||||
key = ' '+key
|
||||
self.raw("JOIN %s%s" % (target,key))
|
||||
if requested:
|
||||
for chan in target.split(' ',1)[0].split(','):
|
||||
if chan == '0':
|
||||
self.requested_parts.update(self.on_channels)
|
||||
else:
|
||||
self.requested_joins.add(self.norm_case(chan))
|
||||
|
||||
def part(self, target, msg="", requested=True):
|
||||
if msg:
|
||||
msg = " :" + msg
|
||||
|
||||
self.raw("PART %s%s" % (target, msg))
|
||||
if requested:
|
||||
for chan in target.split(' ',1)[0].split(','):
|
||||
self.requested_parts.add(self.norm_case(target))
|
||||
|
||||
def msg(self, target, msg):
|
||||
self.raw("PRIVMSG %s :%s" % (target, msg))
|
||||
|
||||
self.events.trigger(
|
||||
'OwnText', source=self.me, target=str(target), text=msg,
|
||||
network=self, window=windows.get_default(self, self.manager)
|
||||
)
|
||||
|
||||
def notice(self, target, msg):
|
||||
self.raw("NOTICE %s :%s" % (target, msg))
|
||||
|
||||
self.events.trigger(
|
||||
'OwnNotice', source=self.me, target=str(target), text=msg,
|
||||
network=self, window=windows.get_default(self)
|
||||
)
|
||||
|
||||
#a Network that is never connected
|
||||
class DummyNetwork(Network):
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
|
||||
def __init__(self, core):
|
||||
Network.__init__(self, core)
|
||||
|
||||
self.name = self.server = self.isupport['NETWORK'] = "None"
|
||||
|
||||
def connect(self):
|
||||
raise NotImplementedError, "Cannot connect dummy network."
|
||||
|
||||
def raw(self, msg):
|
||||
raise NotImplementedError, "Cannot send %s over the dummy network." % repr(msg)
|
||||
|
||||
#dummy_network = DummyNetwork()
|
||||
|
||||
#this was ported from srvx's tools.c
|
||||
def match_glob(text, glob, t=0, g=0):
|
||||
while g < len(glob):
|
||||
if glob[g] in '?*':
|
||||
star_p = q_cnt = 0
|
||||
while g < len(glob):
|
||||
if glob[g] == '*':
|
||||
star_p = True
|
||||
elif glob[g] == '?':
|
||||
q_cnt += 1
|
||||
else:
|
||||
break
|
||||
g += 1
|
||||
t += q_cnt
|
||||
if t > len(text):
|
||||
return False
|
||||
if star_p:
|
||||
if g == len(glob):
|
||||
return True
|
||||
for i in xrange(t, len(text)):
|
||||
if text[i] == glob[g] and match_glob(text, glob, i+1, g+1):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
if t == len(text) and g == len(glob):
|
||||
return True
|
||||
if t == len(text) or g == len(glob) or text[t] != glob[g]:
|
||||
return False
|
||||
t += 1
|
||||
g += 1
|
||||
return t == len(text)
|
457
services/console/lib/purk/parse_mirc.py
Normal file
457
services/console/lib/purk/parse_mirc.py
Normal file
@ -0,0 +1,457 @@
|
||||
try:
|
||||
from conf import conf
|
||||
except ImportError:
|
||||
conf = {}
|
||||
|
||||
BOLD = '\x02'
|
||||
UNDERLINE = '\x1F'
|
||||
MIRC_COLOR = '\x03'
|
||||
MIRC_COLOR_BG = MIRC_COLOR, MIRC_COLOR
|
||||
BERS_COLOR = '\x04'
|
||||
RESET = '\x0F'
|
||||
|
||||
colors = (
|
||||
'#FFFFFF', '#000000', '#00007F', '#009300',
|
||||
'#FF0000', '#7F0000', '#9C009C', '#FF7F00',
|
||||
'#FFFF00', '#00FF00', '#009393', '#00FFFF',
|
||||
'#0000FF', '#FF00FF', '#7F7F7F', '#D2D2D2'
|
||||
)
|
||||
|
||||
def get_mirc_color(number):
|
||||
if number == '99':
|
||||
return None
|
||||
|
||||
number = int(number) & 15
|
||||
|
||||
confcolors = conf.get('colors', colors)
|
||||
try:
|
||||
return confcolors[number]
|
||||
except:
|
||||
# someone edited their colors wrongly
|
||||
return colors[number]
|
||||
|
||||
DEC_DIGITS, HEX_DIGITS = set('0123456789'), set('0123456789abcdefABCDEF')
|
||||
|
||||
def parse_mirc_color(string, pos, open_tags, tags):
|
||||
color_chars = 1
|
||||
|
||||
if MIRC_COLOR in open_tags:
|
||||
fgtag = open_tags.pop(MIRC_COLOR)
|
||||
fgtag['to'] = pos
|
||||
tags.append(fgtag)
|
||||
|
||||
if MIRC_COLOR_BG in open_tags:
|
||||
bgtag = open_tags.pop(MIRC_COLOR_BG)
|
||||
bgtag['to'] = pos
|
||||
tags.append(bgtag)
|
||||
|
||||
bg = bgtag['data'][1]
|
||||
else:
|
||||
bg = None
|
||||
|
||||
if string[0] in DEC_DIGITS:
|
||||
if string[1] in DEC_DIGITS:
|
||||
fg = get_mirc_color(string[:2])
|
||||
string = string[1:]
|
||||
color_chars += 2
|
||||
|
||||
else:
|
||||
fg = get_mirc_color(string[0])
|
||||
color_chars += 1
|
||||
|
||||
if string[1] == "," and string[2] in DEC_DIGITS:
|
||||
if string[3] in DEC_DIGITS:
|
||||
bg = get_mirc_color(string[2:4])
|
||||
color_chars += 3
|
||||
|
||||
else:
|
||||
bg = get_mirc_color(string[2])
|
||||
color_chars += 2
|
||||
|
||||
else:
|
||||
fg = bg = None
|
||||
|
||||
if fg:
|
||||
open_tags[MIRC_COLOR] = {'data': ("foreground",fg), 'from': pos}
|
||||
else:
|
||||
open_tags.pop(MIRC_COLOR,None)
|
||||
|
||||
if bg:
|
||||
open_tags[MIRC_COLOR_BG] = {'data': ("background",bg), 'from': pos}
|
||||
else:
|
||||
open_tags.pop(MIRC_COLOR_BG,None)
|
||||
|
||||
return color_chars
|
||||
|
||||
def parse_bersirc_color(string, pos, open_tags, tags):
|
||||
bg = None
|
||||
if MIRC_COLOR in open_tags:
|
||||
tag = open_tags.pop(MIRC_COLOR)
|
||||
tag['to'] = pos
|
||||
tags.append(tag)
|
||||
|
||||
if MIRC_COLOR_BG in open_tags:
|
||||
bgtag = open_tags.pop(MIRC_COLOR_BG)
|
||||
bgtag['to'] = pos
|
||||
tags.append(bgtag)
|
||||
|
||||
bg = bgtag['data'][1]
|
||||
|
||||
for c in (0, 1, 2, 3, 4, 5):
|
||||
if string[c] not in HEX_DIGITS:
|
||||
return 1
|
||||
fg = '#' + string[:6].upper()
|
||||
|
||||
color_chars = 7
|
||||
for c in (7, 8, 9, 10, 11, 12):
|
||||
if string[c] not in HEX_DIGITS:
|
||||
break
|
||||
else:
|
||||
if string[6] == ",":
|
||||
bg = '#' + string[7:13].upper()
|
||||
color_chars = 14
|
||||
|
||||
if fg:
|
||||
open_tags[MIRC_COLOR] = {'data': ("foreground",fg), 'from': pos}
|
||||
else:
|
||||
open_tags.pop(MIRC_COLOR,None)
|
||||
|
||||
if bg:
|
||||
open_tags[MIRC_COLOR_BG] = {'data': ("background",bg), 'from': pos}
|
||||
else:
|
||||
open_tags.pop(MIRC_COLOR_BG,None)
|
||||
|
||||
return color_chars
|
||||
|
||||
def parse_bold(string, pos, open_tags, tags):
|
||||
if BOLD in open_tags:
|
||||
tag = open_tags.pop(BOLD)
|
||||
tag['to'] = pos
|
||||
tags.append(tag)
|
||||
|
||||
else:
|
||||
open_tags[BOLD] = {'data': ('weight', BOLD), 'from': pos}
|
||||
|
||||
return 1
|
||||
|
||||
def parse_underline(string, pos, open_tags, tags):
|
||||
if UNDERLINE in open_tags:
|
||||
tag = open_tags.pop(UNDERLINE)
|
||||
tag['to'] = pos
|
||||
tags.append(tag)
|
||||
|
||||
else:
|
||||
open_tags[UNDERLINE] = {'data': ('underline', UNDERLINE), 'from': pos}
|
||||
|
||||
return 1
|
||||
|
||||
def parse_reset(string, pos, open_tags, tags):
|
||||
for t in open_tags:
|
||||
tag = open_tags[t]
|
||||
tag['to'] = pos
|
||||
tags.append(tag)
|
||||
|
||||
open_tags.clear()
|
||||
|
||||
return 1
|
||||
|
||||
tag_parser = {
|
||||
MIRC_COLOR: parse_mirc_color,
|
||||
BERS_COLOR: parse_bersirc_color,
|
||||
BOLD: parse_bold,
|
||||
UNDERLINE: parse_underline,
|
||||
RESET: parse_reset
|
||||
}
|
||||
|
||||
def parse_mirc(string):
|
||||
string += RESET
|
||||
|
||||
out = ''
|
||||
open_tags = {}
|
||||
tags = []
|
||||
text_i = outtext_i = 0
|
||||
|
||||
for tag_i, char in enumerate(string):
|
||||
if char in tag_parser:
|
||||
out += string[text_i:tag_i]
|
||||
|
||||
outtext_i += tag_i - text_i
|
||||
|
||||
text_i = tag_i + tag_parser[char](
|
||||
string[tag_i+1:],
|
||||
outtext_i,
|
||||
open_tags,
|
||||
tags
|
||||
)
|
||||
|
||||
return tags, out
|
||||
|
||||
#transforms for unparse_mirc
|
||||
|
||||
#^O
|
||||
def transform_reset(start, end):
|
||||
return RESET, '', {}
|
||||
|
||||
#^K
|
||||
def transform_color_reset(start, end):
|
||||
if ('foreground' in start and 'foreground' not in end) or \
|
||||
('background' in start and 'background' not in end):
|
||||
result = start.copy()
|
||||
result.pop("foreground",None)
|
||||
result.pop("background",None)
|
||||
return MIRC_COLOR, DEC_DIGITS, result
|
||||
else:
|
||||
return '','',start
|
||||
|
||||
#^KXX
|
||||
def transform_color(start, end):
|
||||
if (start.get('foreground',99) != end.get('foreground',99)):
|
||||
confcolors = conf.get('colors', colors)
|
||||
result = start.copy()
|
||||
if 'foreground' in end:
|
||||
try:
|
||||
index = list(confcolors).index(end['foreground'].upper())
|
||||
except ValueError:
|
||||
return '','',start
|
||||
result['foreground'] = end['foreground']
|
||||
else:
|
||||
index = 99
|
||||
del result['foreground']
|
||||
return '\x03%02i' % index, ',', result
|
||||
else:
|
||||
return '','',start
|
||||
|
||||
#^KXX,YY
|
||||
def transform_bcolor(start, end):
|
||||
if (start.get('background',99) != end.get('background',99)):
|
||||
confcolors = conf.get('colors', colors)
|
||||
result = start.copy()
|
||||
if 'foreground' in end:
|
||||
try:
|
||||
fg_index = list(confcolors).index(end['foreground'].upper())
|
||||
except ValueError:
|
||||
return '','',start
|
||||
result['foreground'] = end['foreground']
|
||||
else:
|
||||
fg_index = 99
|
||||
result.pop('foreground',None)
|
||||
if 'background' in end:
|
||||
try:
|
||||
bg_index = list(confcolors).index(end['background'].upper())
|
||||
except ValueError:
|
||||
return '','',start
|
||||
result['background'] = end['background']
|
||||
else:
|
||||
bg_index = 99
|
||||
del result['background']
|
||||
return '\x03%02i,%02i' % (fg_index, bg_index), ',', result
|
||||
else:
|
||||
return '','',start
|
||||
|
||||
#^LXXXXXX
|
||||
def transform_bersirc(start, end):
|
||||
if 'foreground' in end and end['foreground'] != start.get('foreground'):
|
||||
result = start.copy()
|
||||
result['foreground'] = end['foreground']
|
||||
return "\x04%s" % end['foreground'][1:], ',', result
|
||||
else:
|
||||
return '','',start
|
||||
|
||||
#^LXXXXXX,YYYYYY
|
||||
def transform_bbersirc(start, end):
|
||||
if 'foreground' in end and 'background' in end and (
|
||||
end['foreground'] != start.get('foreground') or
|
||||
end['background'] != start.get('background')):
|
||||
result = start.copy()
|
||||
result['foreground'] = end['foreground']
|
||||
result['background'] = end['background']
|
||||
return "\x04%s,%s" % (end['foreground'][1:], end['background'][1:]), ',', result
|
||||
else:
|
||||
return '','',start
|
||||
|
||||
|
||||
#^B
|
||||
def transform_underline(start, end):
|
||||
if ('underline' in start) != ('underline' in end):
|
||||
result = start.copy()
|
||||
if 'underline' in start:
|
||||
del result['underline']
|
||||
else:
|
||||
result['underline'] = UNDERLINE
|
||||
return UNDERLINE, '', result
|
||||
else:
|
||||
return '','',start
|
||||
|
||||
#^U
|
||||
def transform_bold(start, end):
|
||||
if ('weight' in start) != ('weight' in end):
|
||||
result = start.copy()
|
||||
if 'weight' in start:
|
||||
del result['weight']
|
||||
else:
|
||||
result['weight'] = BOLD
|
||||
return BOLD, '', result
|
||||
else:
|
||||
return '','',start
|
||||
|
||||
#^B^B
|
||||
#In some rare circumstances, we HAVE to do this to generate a working string
|
||||
def transform_dbold(start, end):
|
||||
return BOLD*2, '', start
|
||||
|
||||
#return the formatting needed to transform one set of format tags to another
|
||||
def transform(start, end, nextchar=" "):
|
||||
transform_functions = (
|
||||
transform_reset, transform_color_reset, transform_color, transform_bcolor,
|
||||
transform_bersirc, transform_bbersirc, transform_underline,
|
||||
transform_bold, transform_dbold,
|
||||
)
|
||||
|
||||
candidates = [('','',start)]
|
||||
result = None
|
||||
|
||||
for f in transform_functions:
|
||||
for string, badchars, s in candidates[:]:
|
||||
newstring, badchars, s = f(s, end)
|
||||
string += newstring
|
||||
if newstring and (result == None or len(string) < len(result)):
|
||||
if nextchar not in badchars and s == end:
|
||||
result = string
|
||||
else:
|
||||
candidates.append((string, badchars, s))
|
||||
return result
|
||||
|
||||
def unparse_mirc(tagsandtext):
|
||||
lasttags, lastchar = {}, ''
|
||||
|
||||
string = []
|
||||
for tags, char in tagsandtext:
|
||||
if tags != lasttags:
|
||||
string.append(transform(lasttags, tags, char[0]))
|
||||
string.append(char)
|
||||
lasttags, lastchar = tags, char
|
||||
return ''.join(string)
|
||||
|
||||
if __name__ == "__main__":
|
||||
tests = [
|
||||
'not\x02bold\x02not',
|
||||
'not\x1Funderline\x1Fnot',
|
||||
|
||||
"\x02\x1FHi\x0F",
|
||||
|
||||
'not\x030,17white-on-black\x0304red-on-black\x03nothing',
|
||||
|
||||
"\x040000CC<\x04nick\x040000CC>\x04 text",
|
||||
|
||||
'\x04770077,FFFFFFbersirc color with background! \x04000077setting foreground! \x04reset!',
|
||||
|
||||
'\x047700,FFFFbersirc',
|
||||
|
||||
"\x03123Hello",
|
||||
|
||||
"\x0312,Hello",
|
||||
|
||||
"\x034Hello",
|
||||
|
||||
"Bo\x02ld",
|
||||
|
||||
"\x034,5Hello\x036Goodbye",
|
||||
|
||||
"\x04ff0000,00ff00Hello\x040000ffGoodbye",
|
||||
|
||||
"\x04777777(\x0400CCCCstuff\x04777777)\x04",
|
||||
|
||||
'\x0307orange\x04CCCCCCgrey\x0307orange',
|
||||
|
||||
'\x04CCCCCC,444444sdf\x0304jkl',
|
||||
|
||||
'\x0403\x02\x02,trixy',
|
||||
|
||||
'\x04FFFFFF\x02\x02,000000trixy for bersirc',
|
||||
]
|
||||
|
||||
results = [
|
||||
([{'from': 3, 'data': ('weight', '\x02'), 'to': 7}], 'notboldnot'),
|
||||
|
||||
([{'from': 3, 'data': ('underline', '\x1f'), 'to': 12}], 'notunderlinenot'),
|
||||
|
||||
([{'from': 0, 'data': ('weight', '\x02'), 'to': 2}, {'from': 0, 'data': ('underline', '\x1f'), 'to': 2}], 'Hi'),
|
||||
|
||||
([{'from': 3, 'data': ('foreground', '#FFFFFF'), 'to': 17}, {'from': 3, 'data': ('background', '#000000'), 'to': 17}, {'from': 17, 'data': ('foreground', '#FF0000'), 'to': 29}, {'from': 17, 'data': ('background', '#000000'), 'to': 29}], 'notwhite-on-blackred-on-blacknothing'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#0000CC'), 'to': 1}, {'from': 5, 'data': ('foreground', '#0000CC'), 'to': 6}], '<nick> text'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#770077'), 'to': 31}, {'from': 0, 'data': ('background', '#FFFFFF'), 'to': 31}, {'from': 31, 'data': ('foreground', '#000077'), 'to': 51}, {'from': 31, 'data': ('background', '#FFFFFF'), 'to': 51}], 'bersirc color with background! setting foreground! reset!'),
|
||||
|
||||
([], '7700,FFFFbersirc'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#0000FF'), 'to': 6}], '3Hello'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#0000FF'), 'to': 6}], ',Hello'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#FF0000'), 'to': 5}], 'Hello'),
|
||||
|
||||
([{'from': 2, 'data': ('weight', '\x02'), 'to': 4}], 'Bold'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#FF0000'), 'to': 5}, {'from': 0, 'data': ('background', '#7F0000'), 'to': 5}, {'from': 5, 'data': ('foreground', '#9C009C'), 'to': 12}, {'from': 5, 'data': ('background', '#7F0000'), 'to': 12}], 'HelloGoodbye'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#FF0000'), 'to': 5}, {'from': 0, 'data': ('background', '#00FF00'), 'to': 5}, {'from': 5, 'data': ('foreground', '#0000FF'), 'to': 12}, {'from': 5, 'data': ('background', '#00FF00'), 'to': 12}], 'HelloGoodbye'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#777777'), 'to': 1}, {'from': 1, 'data': ('foreground', '#00CCCC'), 'to': 6}, {'from': 6, 'data': ('foreground', '#777777'), 'to': 7}], '(stuff)'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#FF7F00'), 'to': 6}, {'from': 6, 'data': ('foreground', '#CCCCCC'), 'to': 10}, {'from': 10, 'data': ('foreground', '#FF7F00'), 'to': 16}], 'orangegreyorange'),
|
||||
|
||||
([{'from': 0, 'data': ('foreground', '#CCCCCC'), 'to': 3}, {'from': 0, 'data': ('background', '#444444'), 'to': 3}, {'from': 3, 'data': ('foreground', '#FF0000'), 'to': 6}, {'from': 3, 'data': ('background', '#444444'), 'to': 6}], 'sdfjkl'),
|
||||
|
||||
([{'from': 2, 'data': ('weight', '\x02'), 'to': 2}], '03,trixy'),
|
||||
|
||||
([{'from': 0, 'data': ('weight', '\x02'), 'to': 0}, {'from': 0, 'data': ('foreground', '#FFFFFF'), 'to': 24}], ',000000trixy for bersirc'),
|
||||
]
|
||||
|
||||
#"""
|
||||
|
||||
#r = range(20000)
|
||||
#for i in r:
|
||||
# for test in tests:
|
||||
# parse_mirc(test)
|
||||
|
||||
"""
|
||||
|
||||
lines = [eval(line.strip()) for line in file("parse_mirc_torture_test.txt")]
|
||||
|
||||
for r in range(100):
|
||||
for line in lines:
|
||||
parse_mirc(line)
|
||||
|
||||
#"""
|
||||
|
||||
def setify_tags(tags):
|
||||
return set(frozenset(tag.iteritems()) for tag in tags if tag['from'] != tag['to'])
|
||||
|
||||
def parsed_eq((tags1, text1), (tags2, text2)):
|
||||
return setify_tags(tags1) == setify_tags(tags2) and text1 == text2
|
||||
|
||||
def parsed_to_unparsed((tags, text)):
|
||||
result = []
|
||||
for i, char in enumerate(text):
|
||||
result.append((
|
||||
dict(tag['data'] for tag in tags if tag['from'] <= i < tag['to']),
|
||||
char))
|
||||
return result
|
||||
|
||||
for i, (test, result) in enumerate(zip(tests, results)):
|
||||
if not parsed_eq(parse_mirc(test), result):
|
||||
print "parse_mirc failed test %s:" % i
|
||||
print repr(test)
|
||||
print parse_mirc(test)
|
||||
print result
|
||||
print
|
||||
|
||||
elif not parsed_eq(parse_mirc(unparse_mirc(parsed_to_unparsed(result))), result):
|
||||
print "unparse_mirc failed test %s:" % i
|
||||
print repr(test)
|
||||
print unparse_mirc(test)
|
||||
print
|
||||
|
||||
#import dis
|
||||
#dis.dis(parse_mirc)
|
16
services/console/lib/purk/scripts/Makefile.am
Normal file
16
services/console/lib/purk/scripts/Makefile.am
Normal file
@ -0,0 +1,16 @@
|
||||
sugardir = $(pkgdatadir)/services/console/lib/purk/scripts
|
||||
|
||||
sugar_PYTHON = \
|
||||
alias.py \
|
||||
chaninfo.py \
|
||||
clicks.py \
|
||||
completion.py \
|
||||
console.py \
|
||||
history.py \
|
||||
ignore.py \
|
||||
irc_script.py \
|
||||
keys.py \
|
||||
theme.py \
|
||||
timeout.py \
|
||||
ui_script.py
|
||||
|
60
services/console/lib/purk/scripts/alias.py
Executable file
60
services/console/lib/purk/scripts/alias.py
Executable file
@ -0,0 +1,60 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
from conf import conf
|
||||
|
||||
aliases = conf.get("aliases",{
|
||||
'op':'"mode "+window.id+" +"+"o"*len(args)+" "+" ".join(args)',
|
||||
'deop':'"mode "+window.id+" -"+"o"*len(args)+" "+" ".join(args)',
|
||||
'voice':'"mode "+window.id+" +"+"v"*len(args)+" "+" ".join(args)',
|
||||
'devoice':'"mode "+window.id+" -"+"v"*len(args)+" "+" ".join(args)',
|
||||
'umode':'"mode "+network.me+" "+" ".join(args)',
|
||||
'clear':'window.output.clear()',
|
||||
})
|
||||
|
||||
class CommandHandler:
|
||||
__slots__ = ["command"]
|
||||
def __init__(self, command):
|
||||
self.command = command
|
||||
def __call__(self, e):
|
||||
loc = sys.modules.copy()
|
||||
loc.update(e.__dict__)
|
||||
result = eval(self.command,loc)
|
||||
if isinstance(result,basestring):
|
||||
core.events.run(result,e.window,e.network)
|
||||
|
||||
for name in aliases:
|
||||
globals()['onCommand'+name.capitalize()] = CommandHandler(aliases[name])
|
||||
|
||||
def onCommandAlias(e):
|
||||
if e.args and 'r' in e.switches:
|
||||
name = e.args[0].lower()
|
||||
command = aliases[name]
|
||||
del aliases[name]
|
||||
conf['aliases'] = aliases
|
||||
e.window.write("* Deleted alias %s%s (was %s)" % (conf.get('command-prefix','/'),name,command))
|
||||
core.events.load(__name__,reloading=True)
|
||||
elif 'l' in e.switches:
|
||||
e.window.write("* Current aliases:")
|
||||
for i in aliases:
|
||||
e.window.write("* %s%s: %s" % (conf.get('command-prefix','/'),i,aliases[i]))
|
||||
elif len(e.args) >= 2:
|
||||
name = e.args[0].lower()
|
||||
command = ' '.join(e.args[1:])
|
||||
aliases[name] = command
|
||||
conf['aliases'] = aliases
|
||||
e.window.write("* Created an alias %s%s to %s" % (conf.get('command-prefix','/'),name,command))
|
||||
core.events.reload(__name__)
|
||||
elif len(e.args) == 1:
|
||||
name = e.args[0].lower()
|
||||
if name in aliases:
|
||||
e.window.write("* %s%s is an alias to %s" % (conf.get('command-prefix','/'),name,aliases[name]))
|
||||
else:
|
||||
e.window.write("* There is no alias %s%s" % (conf.get('command-prefix','/'),name))
|
||||
else:
|
||||
e.window.write(
|
||||
"""Usage:
|
||||
/alias \x02name\x02 \x02expression\x02 to create or replace an alias
|
||||
/alias \x02name\x02 to look at an alias
|
||||
/alias -r \x02name\x02 to remove an alias
|
||||
/alias -l to see a list of aliases""")
|
320
services/console/lib/purk/scripts/chaninfo.py
Normal file
320
services/console/lib/purk/scripts/chaninfo.py
Normal file
@ -0,0 +1,320 @@
|
||||
import windows
|
||||
|
||||
def _justprefix(network, channel, nick):
|
||||
fr, to = network.isupport["PREFIX"][1:].split(")")
|
||||
|
||||
for mode, prefix in zip(fr, to):
|
||||
if mode in channel.nicks.get(nick, ''):
|
||||
return prefix
|
||||
|
||||
return ''
|
||||
|
||||
def prefix(network, channelname, nick):
|
||||
channel = getchan(network, channelname)
|
||||
|
||||
if channel:
|
||||
nick = '%s%s' % (_justprefix(network, channel, nick), nick)
|
||||
|
||||
return nick
|
||||
|
||||
def escape(string):
|
||||
for escapes in (('&','&'), ('<','<'), ('>','>')):
|
||||
string = string.replace(*escapes)
|
||||
return string
|
||||
|
||||
def sortkey(network, channelname, nick):
|
||||
chanmodes, dummy = network.isupport["PREFIX"][1:].split(")")
|
||||
nickmodes = mode(network, channelname, nick)
|
||||
|
||||
return '%s%s' % (''.join(str(int(mode not in nickmodes)) for mode in chanmodes), network.norm_case(nick))
|
||||
|
||||
def nicklist_add(network, channel, nick):
|
||||
window = windows.get(windows.ChannelWindow, network, channel.name, core)
|
||||
#window = core.window
|
||||
if window:
|
||||
window.nicklist.append(nick, escape(prefix(network, channel.name, nick)), sortkey(network, channel.name, nick))
|
||||
|
||||
def nicklist_del(network, channel, nick):
|
||||
window = windows.get(windows.ChannelWindow, network, channel.name, core)
|
||||
#window = core.window
|
||||
if window:
|
||||
try:
|
||||
window.nicklist.remove(nick)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def setupListRightClick(e):
|
||||
if isinstance(e.window, windows.ChannelWindow):
|
||||
#if isinstance(core.window, windows.ChannelWindow):
|
||||
#if e.data[0] in e.window.network.isupport["PREFIX"].split(")")[1]:
|
||||
if e.data[0] in core.window.network.isupport["PREFIX"].split(")")[1]:
|
||||
e.nick = e.data[1:]
|
||||
else:
|
||||
e.nick = e.data
|
||||
|
||||
def setupSocketConnect(e):
|
||||
e.network.channels = {}
|
||||
|
||||
def setdownDisconnect(e):
|
||||
e.network.channels = {}
|
||||
|
||||
class Channel(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.nicks = {}
|
||||
self.normal_nicks = {} # mapping of normal nicks to actual nicks
|
||||
self.getting_names = False #are we between lines in a /names reply?
|
||||
self.mode = ''
|
||||
self.special_mode = {} #for limits, keys, and anything similar
|
||||
self.topic = ''
|
||||
self.got_mode = False #did we get at least one mode reply?
|
||||
self.got_names = False #did we get at least one names reply?
|
||||
|
||||
def getchan(network, channel):
|
||||
return hasattr(network, 'channels') and network.channels.get(network.norm_case(channel))
|
||||
|
||||
#return a list of channels you're on on the given network
|
||||
def channels(network):
|
||||
if not hasattr(network, 'channels'):
|
||||
network.channels = {}
|
||||
|
||||
return list(network.channels)
|
||||
|
||||
#return True if you're on the channel
|
||||
def ischan(network, channel):
|
||||
return bool(getchan(network, channel))
|
||||
|
||||
#return True if the nick is on the channel
|
||||
def ison(network, channel, nickname):
|
||||
channel = getchan(network, channel)
|
||||
return channel and network.norm_case(nickname) in channel.normal_nicks
|
||||
|
||||
#return a list of nicks on the given channel
|
||||
def nicks(network, channel):
|
||||
channel = getchan(network, channel)
|
||||
|
||||
if channel:
|
||||
return channel.nicks
|
||||
else:
|
||||
return {}
|
||||
|
||||
#return the mode on the given channel
|
||||
def mode(network, channel, nickname=''):
|
||||
channel = getchan(network, channel)
|
||||
|
||||
if channel:
|
||||
if nickname:
|
||||
realnick = channel.normal_nicks.get(network.norm_case(nickname))
|
||||
if realnick:
|
||||
return channel.nicks[realnick]
|
||||
|
||||
else:
|
||||
result = channel.mode
|
||||
for m in channel.mode:
|
||||
if m in channel.special_mode:
|
||||
result += ' '+channel.special_mode[m]
|
||||
return result
|
||||
|
||||
return ''
|
||||
|
||||
#return the topic on the given channel
|
||||
def topic(network, channel):
|
||||
channel = getchan(network, channel)
|
||||
|
||||
if channel:
|
||||
return channel.topic
|
||||
else:
|
||||
return ''
|
||||
|
||||
def setupJoin(e):
|
||||
print e
|
||||
if e.source == e.network.me:
|
||||
e.network.channels[e.network.norm_case(e.target)] = Channel(e.target)
|
||||
e.network.raw('MODE '+e.target)
|
||||
|
||||
#if we wanted to be paranoid, we'd account for not being on the channel
|
||||
channel = getchan(e.network,e.target)
|
||||
channel.nicks[e.source] = ''
|
||||
channel.normal_nicks[e.network.norm_case(e.source)] = e.source
|
||||
|
||||
if e.source == e.network.me:
|
||||
#If the channel window already existed, and we're joining, then we
|
||||
#didn't clear out the nicklist when we left. That means we have to clear
|
||||
#it out now.
|
||||
window = windows.get(windows.ChannelWindow, e.network, e.target, core)
|
||||
#window = core.window
|
||||
#print core
|
||||
if window:
|
||||
window.nicklist.clear()
|
||||
|
||||
nicklist_add(e.network, channel, e.source)
|
||||
|
||||
def setdownPart(e):
|
||||
if e.source == e.network.me:
|
||||
del e.network.channels[e.network.norm_case(e.target)]
|
||||
else:
|
||||
channel = getchan(e.network,e.target)
|
||||
nicklist_del(e.network, channel, e.source)
|
||||
del channel.nicks[e.source]
|
||||
del channel.normal_nicks[e.network.norm_case(e.source)]
|
||||
|
||||
def setdownKick(e):
|
||||
if e.target == e.network.me:
|
||||
del e.network.channels[e.network.norm_case(e.channel)]
|
||||
else:
|
||||
channel = getchan(e.network,e.channel)
|
||||
nicklist_del(e.network, channel, e.target)
|
||||
del channel.nicks[e.target]
|
||||
del channel.normal_nicks[e.network.norm_case(e.target)]
|
||||
|
||||
def setdownQuit(e):
|
||||
#if paranoid: check if e.source is me
|
||||
for channame in channels(e.network):
|
||||
channel = getchan(e.network,channame)
|
||||
if e.source in channel.nicks:
|
||||
nicklist_del(e.network, channel, e.source)
|
||||
del channel.nicks[e.source]
|
||||
del channel.normal_nicks[e.network.norm_case(e.source)]
|
||||
|
||||
def setupMode(e):
|
||||
channel = getchan(e.network,e.channel)
|
||||
if channel:
|
||||
user_modes = e.network.isupport['PREFIX'].split(')')[0][1:]
|
||||
|
||||
(list_modes,
|
||||
always_parm_modes,
|
||||
set_parm_modes,
|
||||
normal_modes) = e.network.isupport['CHANMODES'].split(',')
|
||||
|
||||
list_modes += user_modes
|
||||
|
||||
mode_on = True #are we reading a + section or a - section?
|
||||
params = e.text.split(' ')
|
||||
|
||||
for char in params.pop(0):
|
||||
if char == '+':
|
||||
mode_on = True
|
||||
|
||||
elif char == '-':
|
||||
mode_on = False
|
||||
|
||||
else:
|
||||
if char in user_modes:
|
||||
#these are modes like op and voice
|
||||
nickname = params.pop(0)
|
||||
nicklist_del(e.network, channel, nickname)
|
||||
if mode_on:
|
||||
channel.nicks[nickname] += char
|
||||
else:
|
||||
channel.nicks[nickname] = channel.nicks[nickname].replace(char, '')
|
||||
nicklist_add(e.network, channel, nickname)
|
||||
|
||||
elif char in list_modes:
|
||||
#things like ban/unban
|
||||
#FIXME: We don't keep track of those lists here, but we know
|
||||
# when they're changed and how. Scriptors should be able to
|
||||
# take advantage of this
|
||||
params.pop(0)
|
||||
|
||||
elif char in always_parm_modes:
|
||||
#these always have a parameter
|
||||
param = params.pop(0)
|
||||
|
||||
if mode_on:
|
||||
channel.special_mode[char] = param
|
||||
else:
|
||||
#account for unsetting modes that aren't set
|
||||
channel.special_mode.pop(char, None)
|
||||
|
||||
elif char in set_parm_modes:
|
||||
#these have a parameter only if they're being set
|
||||
if mode_on:
|
||||
channel.special_mode[char] = params.pop(0)
|
||||
else:
|
||||
#account for unsetting modes that aren't set
|
||||
channel.special_mode.pop(char, None)
|
||||
|
||||
if char not in list_modes:
|
||||
if mode_on:
|
||||
channel.mode = channel.mode.replace(char, '')+char
|
||||
else:
|
||||
channel.mode = channel.mode.replace(char, '')
|
||||
|
||||
def setdownNick(e):
|
||||
for channame in channels(e.network):
|
||||
channel = getchan(e.network,channame)
|
||||
if e.source in channel.nicks:
|
||||
nicklist_del(e.network, channel, e.source)
|
||||
del channel.normal_nicks[e.network.norm_case(e.source)]
|
||||
channel.nicks[e.target] = channel.nicks[e.source]
|
||||
del channel.nicks[e.source]
|
||||
channel.normal_nicks[e.network.norm_case(e.target)] = e.target
|
||||
nicklist_add(e.network, channel, e.target)
|
||||
|
||||
def setupTopic(e):
|
||||
channel = getchan(e.network, e.target)
|
||||
if channel:
|
||||
channel.topic = e.text
|
||||
|
||||
def setupRaw(e):
|
||||
if e.msg[1] == '353': #names reply
|
||||
channel = getchan(e.network,e.msg[4])
|
||||
if channel:
|
||||
if not channel.getting_names:
|
||||
channel.nicks.clear()
|
||||
channel.normal_nicks.clear()
|
||||
channel.getting_names = True
|
||||
if not channel.got_names:
|
||||
e.quiet = True
|
||||
for nickname in e.msg[5].split(' '):
|
||||
if nickname:
|
||||
if not nickname[0].isalpha() and nickname[0] in e.network.prefixes:
|
||||
n = nickname[1:]
|
||||
channel.nicks[n] = e.network.prefixes[nickname[0]]
|
||||
channel.normal_nicks[e.network.norm_case(n)] = n
|
||||
else:
|
||||
channel.nicks[nickname] = ''
|
||||
channel.normal_nicks[e.network.norm_case(nickname)] = nickname
|
||||
|
||||
elif e.msg[1] == '366': #end of names reply
|
||||
channel = getchan(e.network,e.msg[3])
|
||||
if channel:
|
||||
if not channel.got_names:
|
||||
e.quiet = True
|
||||
channel.got_names = True
|
||||
channel.getting_names = False
|
||||
|
||||
window = windows.get(windows.ChannelWindow, e.network, e.msg[3], core)
|
||||
if window:
|
||||
window.nicklist.replace(
|
||||
(nick, escape(prefix(e.network, channel.name, nick)), sortkey(e.network, channel.name, nick)) for nick in channel.nicks
|
||||
)
|
||||
|
||||
elif e.msg[1] == '324': #channel mode is
|
||||
channel = getchan(e.network,e.msg[3])
|
||||
if channel:
|
||||
if not channel.got_mode:
|
||||
e.quiet = True
|
||||
channel.got_mode = True
|
||||
mode = e.msg[4]
|
||||
params = e.msg[:4:-1]
|
||||
list_modes, always_parm_modes, set_parm_modes, normal_modes = \
|
||||
e.network.isupport['CHANMODES'].split(',')
|
||||
parm_modes = always_parm_modes + set_parm_modes
|
||||
channel.mode = e.msg[4]
|
||||
channel.special_mode.clear()
|
||||
for char in channel.mode:
|
||||
if char in parm_modes:
|
||||
channel.special_mode[char] = params.pop()
|
||||
|
||||
elif e.msg[1] == '331': #no topic
|
||||
channel = getchan(e.network,e.msg[3])
|
||||
if channel:
|
||||
channel.topic = ''
|
||||
|
||||
elif e.msg[1] == '332': #channel topic is
|
||||
channel = getchan(e.network,e.msg[3])
|
||||
if channel:
|
||||
channel.topic = e.text
|
||||
|
||||
#core.events.load(__name__)
|
146
services/console/lib/purk/scripts/clicks.py
Normal file
146
services/console/lib/purk/scripts/clicks.py
Normal file
@ -0,0 +1,146 @@
|
||||
import ui
|
||||
import windows
|
||||
import chaninfo
|
||||
from conf import conf
|
||||
|
||||
def set_target(e):
|
||||
target_l = e.target.lstrip('@+%.(<')
|
||||
e._target_fr = e.target_fr + len(e.target) - len(target_l)
|
||||
|
||||
target_r = e.target.rstrip('>:,')
|
||||
e._target_to = e.target_to - len(e.target) + len(target_r)
|
||||
|
||||
if target_r.endswith(')'):
|
||||
e._target = e.text[e._target_fr:e._target_to]
|
||||
open_parens = e._target.count('(') - e._target.count(')')
|
||||
while open_parens < 0 and e.text[e._target_to-1] == ')':
|
||||
e._target_to -= 1
|
||||
open_parens += 1
|
||||
|
||||
e._target = e.text[e._target_fr:e._target_to]
|
||||
|
||||
def is_nick(e):
|
||||
return isinstance(e.window, windows.ChannelWindow) and \
|
||||
chaninfo.ison(e.window.network, e.window.id, e._target)
|
||||
|
||||
def is_url(e):
|
||||
def starts(prefix, mindots=1):
|
||||
def prefix_url(target):
|
||||
return target.startswith(prefix) and target.count('.') >= mindots
|
||||
|
||||
return prefix_url
|
||||
|
||||
to_check = [starts(*x) for x in [
|
||||
('http://', 1),
|
||||
('https://', 1),
|
||||
('ftp://', 1),
|
||||
('www', 2),
|
||||
]]
|
||||
|
||||
for check_url in to_check:
|
||||
if check_url(e._target):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_chan(e):
|
||||
# click on a #channel
|
||||
return e.window.network and e._target and \
|
||||
e._target[0] in e.window.network.isupport.get('CHANTYPES', '&#$+')
|
||||
|
||||
def get_autojoin_list(network):
|
||||
perform = conf.get('networks',{}).get(network.name,{}).get('perform',())
|
||||
channels = set()
|
||||
for line in perform:
|
||||
if line.startswith('join ') and ' ' not in line[5:]:
|
||||
channels.update(line[5:].split(','))
|
||||
return channels
|
||||
|
||||
def add_autojoin(network, channel):
|
||||
if 'networks' not in conf:
|
||||
conf['networks'] = {}
|
||||
if network.name not in conf['networks']:
|
||||
conf['networks'][network.name] = {'server': network.server}
|
||||
conf['start_networks'] = conf.get('start_networks',[]) + [network.name]
|
||||
if 'perform' in conf['networks'][network.name]:
|
||||
perform = conf['networks'][network.name]['perform']
|
||||
else:
|
||||
perform = conf['networks'][network.name]['perform'] = []
|
||||
|
||||
for n, line in enumerate(perform):
|
||||
if line.startswith('join ') and ' ' not in line[5:]:
|
||||
perform[n] = "%s,%s" % (line, channel)
|
||||
break
|
||||
else:
|
||||
perform.append('join %s' % channel)
|
||||
|
||||
def make_nick_menu(e, target):
|
||||
def query():
|
||||
core.events.run('query %s' % target, e.window, e.window.network)
|
||||
|
||||
def whois():
|
||||
core.events.run('whois %s' % target, e.window, e.window.network)
|
||||
|
||||
e.menu += [
|
||||
('Query', query),
|
||||
('Whois', whois),
|
||||
(),
|
||||
]
|
||||
|
||||
def onHover(e):
|
||||
set_target(e)
|
||||
|
||||
for is_check in (is_nick, is_url, is_chan):
|
||||
if is_check(e):
|
||||
e.tolink.add((e._target_fr, e._target_to))
|
||||
break
|
||||
|
||||
def onClick(e):
|
||||
set_target(e)
|
||||
|
||||
if is_nick(e):
|
||||
core.events.run('query %s' % e._target, e.window, e.window.network)
|
||||
|
||||
# url of the form http://xxx.xxx or www.xxx.xxx
|
||||
elif is_url(e):
|
||||
if e._target.startswith('www'):
|
||||
e._target = 'http://%s' % e._target
|
||||
ui.open_file(e._target)
|
||||
|
||||
# click on a #channel
|
||||
elif is_chan(e):
|
||||
if not chaninfo.ischan(e.window.network, e._target):
|
||||
e.window.network.join(e._target)
|
||||
window = windows.get(windows.ChannelWindow, e.window.network, e._target)
|
||||
if window:
|
||||
window.activate()
|
||||
|
||||
def onRightClick(e):
|
||||
set_target(e)
|
||||
|
||||
# nick on this channel
|
||||
if is_nick(e):
|
||||
make_nick_menu(e, e._target)
|
||||
|
||||
elif is_url(e):
|
||||
if e._target.startswith('www'):
|
||||
e._target = 'http://%s' % e._target
|
||||
|
||||
def copy_to():
|
||||
# copy to clipboard
|
||||
ui.set_clipboard(e._target)
|
||||
|
||||
e.menu += [('Copy', copy_to)]
|
||||
|
||||
elif is_chan(e):
|
||||
e.channel = e._target
|
||||
e.network = e.window.network
|
||||
core.events.trigger('ChannelMenu', e)
|
||||
|
||||
def onListRightClick(e):
|
||||
if isinstance(e.window, windows.ChannelWindow):
|
||||
make_nick_menu(e, e.nick)
|
||||
|
||||
def onListDoubleClick(e):
|
||||
if isinstance(e.window, windows.ChannelWindow):
|
||||
core.events.run("query %s" % e.target, e.window, e.window.network)
|
135
services/console/lib/purk/scripts/completion.py
Normal file
135
services/console/lib/purk/scripts/completion.py
Normal file
@ -0,0 +1,135 @@
|
||||
import windows
|
||||
import chaninfo
|
||||
from conf import conf
|
||||
|
||||
def channel_completer(window, left, right, text):
|
||||
if isinstance(window, windows.ChannelWindow):
|
||||
yield window.id
|
||||
|
||||
for w in windows.get_with(wclass=windows.ChannelWindow, network=window.network):
|
||||
if w is not window:
|
||||
yield w.id
|
||||
|
||||
for w in windows.get_with(wclass=windows.ChannelWindow):
|
||||
if w.network is not window.network:
|
||||
yield w.id
|
||||
|
||||
# normal server commands
|
||||
srv_commands = ('ping', 'join', 'part', 'mode', 'server', 'kick',
|
||||
'quit', 'nick', 'privmsg', 'notice', 'topic')
|
||||
|
||||
def command_completer(window, left, right, text):
|
||||
for c in srv_commands:
|
||||
yield '/%s' % c
|
||||
|
||||
if 'CMDS' in window.network.isupport:
|
||||
for c in window.network.isupport['CMDS'].split(','):
|
||||
yield '/%s' % c.lower()
|
||||
|
||||
for c in core.events.all_events:
|
||||
if c.startswith('Command') and c != 'Command':
|
||||
yield '/%s' % c[7:].lower()
|
||||
|
||||
def nick_completer(window, left, right, text):
|
||||
if type(window) == windows.QueryWindow:
|
||||
yield window.id
|
||||
|
||||
recent_speakers = getattr(window, 'recent_speakers', ())
|
||||
|
||||
for nick in recent_speakers:
|
||||
if chaninfo.ison(window.network, window.id, nick):
|
||||
yield nick
|
||||
|
||||
for nick in chaninfo.nicks(window.network, window.id):
|
||||
if nick not in recent_speakers:
|
||||
yield nick
|
||||
|
||||
def script_completer(window, left, right, text):
|
||||
return core.events.loaded.iterkeys()
|
||||
|
||||
def network_completer(window, left, right, text):
|
||||
return conf.get('networks', {}).iterkeys()
|
||||
|
||||
def get_completer_for(window):
|
||||
input = window.input
|
||||
|
||||
left, right = input.text[:input.cursor], input.text[input.cursor:]
|
||||
|
||||
text = left.split(' ')[-1]
|
||||
|
||||
while True:
|
||||
suffix = ''
|
||||
if text and text[0] in window.network.isupport.get('CHANTYPES', '#&+'):
|
||||
candidates = channel_completer(window, left, right, text)
|
||||
|
||||
elif input.text.startswith('/reload '):
|
||||
candidates = script_completer(window, left, right, text)
|
||||
|
||||
elif input.text.startswith('/edit '):
|
||||
candidates = script_completer(window, left, right, text)
|
||||
|
||||
elif input.text.startswith('/server '):
|
||||
candidates = network_completer(window, left, right, text)
|
||||
|
||||
elif text.startswith('/'):
|
||||
candidates = command_completer(window, left, right, text)
|
||||
suffix = ' '
|
||||
|
||||
else:
|
||||
candidates = nick_completer(window, left, right, text)
|
||||
|
||||
if left == text:
|
||||
suffix = ': '
|
||||
else:
|
||||
suffix = ' '
|
||||
|
||||
if text:
|
||||
before = left[:-len(text)]
|
||||
else:
|
||||
before = left
|
||||
|
||||
insert_text = '%s%s%s%s' % (before, '%s', suffix, right)
|
||||
cursor_pos = len(before + suffix)
|
||||
|
||||
original = window.input.text, window.input.cursor
|
||||
|
||||
for cand in candidates:
|
||||
if cand.lower().startswith(text.lower()):
|
||||
window.input.text, window.input.cursor = insert_text % cand, cursor_pos + len(cand)
|
||||
yield None
|
||||
|
||||
window.input.text, window.input.cursor = original
|
||||
yield None
|
||||
|
||||
# generator--use recent_completer.next() to continue cycling through whatever
|
||||
recent_completer = None
|
||||
|
||||
def onKeyPress(e):
|
||||
global recent_completer
|
||||
|
||||
if e.key == 'Tab':
|
||||
if not recent_completer:
|
||||
recent_completer = get_completer_for(e.window)
|
||||
|
||||
recent_completer.next()
|
||||
|
||||
else:
|
||||
recent_completer = None
|
||||
|
||||
def onActive(e):
|
||||
global recent_completer
|
||||
|
||||
recent_completer = None
|
||||
|
||||
def onText(e):
|
||||
if chaninfo.ischan(e.network, e.target):
|
||||
if not hasattr(e.window, 'recent_speakers'):
|
||||
e.window.recent_speakers = []
|
||||
|
||||
for nick in e.window.recent_speakers:
|
||||
if nick == e.source or not chaninfo.ison(e.network, e.target, nick):
|
||||
e.window.recent_speakers.remove(nick)
|
||||
|
||||
e.window.recent_speakers.insert(0, e.source)
|
||||
|
||||
onAction = onText
|
68
services/console/lib/purk/scripts/console.py
Executable file
68
services/console/lib/purk/scripts/console.py
Executable file
@ -0,0 +1,68 @@
|
||||
import sys
|
||||
import traceback
|
||||
import windows
|
||||
from conf import conf
|
||||
|
||||
class ConsoleWriter:
|
||||
__slots__ = ['window']
|
||||
def __init__(self, window):
|
||||
self.window = window
|
||||
def write(self, text):
|
||||
try:
|
||||
self.window.write(text, line_ending='')
|
||||
except:
|
||||
self.window.write(traceback.format_exc())
|
||||
|
||||
class ConsoleWindow(windows.SimpleWindow):
|
||||
def __init__(self, network, id):
|
||||
windows.SimpleWindow.__init__(self, network, id)
|
||||
|
||||
writer = ConsoleWriter(self)
|
||||
|
||||
sys.stdout = writer
|
||||
sys.stderr = writer
|
||||
|
||||
self.globals = {'window': self}
|
||||
self.locals = {}
|
||||
|
||||
#this prevents problems (and updates an open console window) on reload
|
||||
#window = None
|
||||
#for window in manager:
|
||||
# if type(window).__name__ == "ConsoleWindow":
|
||||
# window.mutate(ConsoleWindow, window.network, window.id)
|
||||
#del window
|
||||
|
||||
def onClose(e):
|
||||
if isinstance(e.window, ConsoleWindow):
|
||||
sys.stdout = sys.__stdout__
|
||||
sys.stderr = sys.__stderr__
|
||||
|
||||
def onCommandConsole(e):
|
||||
windows.new(ConsoleWindow, None, "console").activate()
|
||||
|
||||
def onCommandSay(e):
|
||||
if isinstance(e.window, ConsoleWindow):
|
||||
import pydoc #fix nonresponsive help() command
|
||||
old_pager, pydoc.pager = pydoc.pager, pydoc.plainpager
|
||||
e.window.globals.update(sys.modules)
|
||||
text = ' '.join(e.args)
|
||||
try:
|
||||
e.window.write(">>> %s" % text)
|
||||
result = eval(text, e.window.globals, e.window.locals)
|
||||
if result is not None:
|
||||
e.window.write(repr(result))
|
||||
e.window.globals['_'] = result
|
||||
except SyntaxError:
|
||||
try:
|
||||
exec text in e.window.globals, e.window.locals
|
||||
except:
|
||||
traceback.print_exc()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pydoc.pager = old_pager
|
||||
else:
|
||||
raise core.events.CommandError("There's no one here to speak to.")
|
||||
|
||||
def onStart(e):
|
||||
if conf.get('start-console'):
|
||||
windows.new(ConsoleWindow, None, "console")
|
45
services/console/lib/purk/scripts/history.py
Normal file
45
services/console/lib/purk/scripts/history.py
Normal file
@ -0,0 +1,45 @@
|
||||
def onKeyPress(e):
|
||||
if not hasattr(e.window, 'history'):
|
||||
e.window.history = [], -1
|
||||
|
||||
if e.key in ('Up', 'Down'):
|
||||
h, i = e.window.history
|
||||
|
||||
if i == -1 and e.window.input.text:
|
||||
h.insert(0, e.window.input.text)
|
||||
i = 0
|
||||
|
||||
if e.key == 'Up':
|
||||
i += 1
|
||||
|
||||
if i < len(h):
|
||||
e.window.history = h, i
|
||||
|
||||
e.window.input.text = h[i]
|
||||
e.window.input.cursor = -1
|
||||
|
||||
else: # e.key == 'Up'
|
||||
i -= 1
|
||||
|
||||
if i > -1:
|
||||
e.window.history = h, i
|
||||
|
||||
e.window.input.text = h[i]
|
||||
e.window.input.cursor = -1
|
||||
|
||||
elif i == -1:
|
||||
e.window.history = h, i
|
||||
|
||||
e.window.input.text = ''
|
||||
e.window.input.cursor = -1
|
||||
|
||||
def onInput(e):
|
||||
if not hasattr(e.window, 'history'):
|
||||
e.window.history = [], -1
|
||||
|
||||
if e.text:
|
||||
h, i = e.window.history
|
||||
|
||||
h.insert(0, e.text)
|
||||
|
||||
e.window.history = h, -1
|
43
services/console/lib/purk/scripts/ignore.py
Executable file
43
services/console/lib/purk/scripts/ignore.py
Executable file
@ -0,0 +1,43 @@
|
||||
from conf import conf
|
||||
import irc
|
||||
|
||||
def preRaw(e):
|
||||
if e.msg[1] in ('PRIVMSG','NOTICE'):
|
||||
address = e.network.norm_case('%s!%s' % (e.source, e.address))
|
||||
for mask in conf.get('ignore_masks',()):
|
||||
if irc.match_glob(address, e.network.norm_case(mask)):
|
||||
core.events.halt()
|
||||
|
||||
def onCommandIgnore(e):
|
||||
if 'ignore_masks' not in conf:
|
||||
conf['ignore_masks'] = []
|
||||
if 'l' in e.switches:
|
||||
for i in conf['ignore_masks']:
|
||||
e.window.write('* %s' % i)
|
||||
elif 'c' in e.switches:
|
||||
del conf['ignore_masks']
|
||||
e.window.write('* Cleared the ignore list.')
|
||||
elif e.args:
|
||||
if '!' in e.args[0] or '*' in e.args[0] or '?' in e.args[0]:
|
||||
mask = e.args[0]
|
||||
else:
|
||||
mask = '%s!*' % e.args[0]
|
||||
if 'r' in e.switches:
|
||||
if mask in conf['ignore_masks']:
|
||||
conf['ignore_masks'].remove(mask)
|
||||
e.window.write('* Removed %s from the ignore list' % e.args[0])
|
||||
else:
|
||||
raise core.events.CommandError("Couldn't find %s in the ignore list" % e.args[0])
|
||||
else:
|
||||
if mask in conf['ignore_masks']:
|
||||
e.window.write('* %s is already ignored' % e.args[0])
|
||||
else:
|
||||
conf['ignore_masks'].append(mask)
|
||||
e.window.write('* Ignoring messages from %s' % e.args[0])
|
||||
else:
|
||||
e.window.write(
|
||||
"""Usage:
|
||||
/ignore \x02nick/mask\x02 to ignore a nickname or mask
|
||||
/ignore -r \x02nick/mask\x02 to stop ignoring a nickname or mask
|
||||
/ignore -l to view the ignore list
|
||||
/ignore -c to clear the ignore list""")
|
588
services/console/lib/purk/scripts/irc_script.py
Normal file
588
services/console/lib/purk/scripts/irc_script.py
Normal file
@ -0,0 +1,588 @@
|
||||
import time
|
||||
|
||||
from conf import conf
|
||||
import ui
|
||||
import windows
|
||||
import irc
|
||||
|
||||
COMMAND_PREFIX = conf.get('command_prefix', '/')
|
||||
|
||||
NICK_SUFFIX = r"`_-\|0123456789"
|
||||
|
||||
_hextochr = dict(('%02x' % i, chr(i)) for i in range(256))
|
||||
def unquote(url, rurl=""):
|
||||
|
||||
while '%' in url:
|
||||
url, char = url.rsplit('%', 1)
|
||||
|
||||
chars = char[:2].lower()
|
||||
|
||||
if chars in _hextochr:
|
||||
rurl = '%s%s%s' % (_hextochr[chars], char[2:], rurl)
|
||||
else:
|
||||
rurl = "%s%s%s" % ('%', char, rurl)
|
||||
|
||||
return url + rurl
|
||||
|
||||
#for getting a list of alternative nicks to try on a network
|
||||
def _nick_generator(network):
|
||||
for nick in network.nicks[1:]:
|
||||
yield nick
|
||||
if network._nick_error:
|
||||
nick = 'ircperson'
|
||||
else:
|
||||
nick = network.nicks[0]
|
||||
import itertools
|
||||
for i in itertools.count(1):
|
||||
for j in xrange(len(NICK_SUFFIX)**i):
|
||||
suffix = ''.join(NICK_SUFFIX[(j/(len(NICK_SUFFIX)**x))%len(NICK_SUFFIX)] for x in xrange(i))
|
||||
if network._nick_max_length:
|
||||
yield nick[0:network._nick_max_length-i]+suffix
|
||||
else:
|
||||
yield nick+suffix
|
||||
|
||||
def setdownRaw(e):
|
||||
if not e.done:
|
||||
if not e.network.got_nick:
|
||||
if e.msg[1] in ('432','433','436','437'): #nickname unavailable
|
||||
failednick = e.msg[3]
|
||||
nicks = list(e.network.nicks)
|
||||
|
||||
if hasattr(e.network,'_nick_generator'):
|
||||
if len(failednick) < len(e.network._next_nick):
|
||||
e.network._nick_max_length = len(failednick)
|
||||
e.network._next_nick = e.network._nick_generator.next()
|
||||
e.network.raw('NICK %s' % e.network._next_nick)
|
||||
e.network._nick_error |= (e.msg[1] == '432')
|
||||
else:
|
||||
e.network._nick_error = (e.msg[1] == '432')
|
||||
if len(failednick) < len(e.network.nicks[0]):
|
||||
e.network._nick_max_length = len(failednick)
|
||||
else:
|
||||
e.network._nick_max_length = 0
|
||||
e.network._nick_generator = _nick_generator(e.network)
|
||||
e.network._next_nick = e.network._nick_generator.next()
|
||||
e.network.raw('NICK %s' % e.network._next_nick)
|
||||
|
||||
elif e.msg[1] == '431': #no nickname given--this shouldn't happen
|
||||
pass
|
||||
|
||||
elif e.msg[1] == '001':
|
||||
e.network.got_nick = True
|
||||
if e.network.me != e.msg[2]:
|
||||
core.events.trigger(
|
||||
'Nick', network=e.network, window=e.window,
|
||||
source=e.network.me, target=e.msg[2], address='',
|
||||
text=e.msg[2]
|
||||
)
|
||||
e.network.me = e.msg[2]
|
||||
if hasattr(e.network,'_nick_generator'):
|
||||
del e.network._nick_generator, e.network._nick_max_length, e.network._next_nick
|
||||
|
||||
if e.msg[1] == "PING":
|
||||
e.network.raw("PONG :%s" % e.msg[-1])
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] == "JOIN":
|
||||
e.channel = e.target
|
||||
e.requested = e.network.norm_case(e.channel) in e.network.requested_joins
|
||||
core.events.trigger("Join", e)
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] == "PART":
|
||||
e.channel = e.target
|
||||
e.requested = e.network.norm_case(e.channel) in e.network.requested_parts
|
||||
e.text = ' '.join(e.msg[3:])
|
||||
core.events.trigger("Part", e)
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] in "MODE":
|
||||
e.channel = e.target
|
||||
e.text = ' '.join(e.msg[3:])
|
||||
core.events.trigger("Mode", e)
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] == "QUIT":
|
||||
core.events.trigger('Quit', e)
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] == "KICK":
|
||||
e.channel = e.msg[2]
|
||||
e.target = e.msg[3]
|
||||
core.events.trigger('Kick', e)
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] == "NICK":
|
||||
core.events.trigger('Nick', e)
|
||||
if e.network.me == e.source:
|
||||
e.network.me = e.target
|
||||
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] == "PRIVMSG":
|
||||
core.events.trigger('Text', e)
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] == "NOTICE":
|
||||
core.events.trigger('Notice', e)
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] == "TOPIC":
|
||||
core.events.trigger('Topic', e)
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] in ("376", "422"): #RPL_ENDOFMOTD
|
||||
if e.network.status == irc.INITIALIZING:
|
||||
e.network.status = irc.CONNECTED
|
||||
core.events.trigger('Connect', e)
|
||||
e.done = True
|
||||
|
||||
elif e.msg[1] == "470": #forwarded from channel X to channel Y
|
||||
if e.network.norm_case(e.msg[3]) in e.network.requested_joins:
|
||||
e.network.requested_joins.discard(e.network.norm_case(e.msg[3]))
|
||||
e.network.requested_joins.add(e.network.norm_case(e.msg[4]))
|
||||
|
||||
elif e.msg[1] == "005": #RPL_ISUPPORT
|
||||
for arg in e.msg[3:]:
|
||||
if ' ' not in arg: #ignore "are supported by this server"
|
||||
if '=' in arg:
|
||||
name, value = arg.split('=', 1)
|
||||
if value.isdigit():
|
||||
value = int(value)
|
||||
else:
|
||||
name, value = arg, ''
|
||||
|
||||
#Workaround for broken servers (bahamut on EnterTheGame)
|
||||
if name == 'PREFIX' and value[0] != '(':
|
||||
continue
|
||||
|
||||
#in theory, we're supposed to replace \xHH with the
|
||||
# corresponding ascii character, but I don't think anyone
|
||||
# really does this
|
||||
e.network.isupport[name] = value
|
||||
|
||||
if name == 'PREFIX':
|
||||
new_prefixes = {}
|
||||
modes, prefixes = value[1:].split(')')
|
||||
for mode, prefix in zip(modes, prefixes):
|
||||
new_prefixes[mode] = prefix
|
||||
new_prefixes[prefix] = mode
|
||||
e.network.prefixes = new_prefixes
|
||||
|
||||
def setupSocketConnect(e):
|
||||
e.network.got_nick = False
|
||||
e.network.isupport = {
|
||||
'NETWORK': e.network.server,
|
||||
'PREFIX': '(ohv)@%+',
|
||||
'CHANMODES': 'b,k,l,imnpstr',
|
||||
}
|
||||
e.network.prefixes = {'o':'@', 'h':'%', 'v':'+', '@':'o', '%':'h', '+':'v'}
|
||||
e.network.connect_timestamp = time.time()
|
||||
e.network.requested_joins.clear()
|
||||
e.network.requested_parts.clear()
|
||||
e.network.on_channels.clear()
|
||||
if hasattr(e.network,'_nick_generator'):
|
||||
del e.network._nick_generator, e.network._nick_max_length, e.network._next_nick
|
||||
if not e.done:
|
||||
#this needs to be tested--anyone have a server that uses PASS?
|
||||
if e.network.password:
|
||||
e.network.raw("PASS :%s" % e.network.password)
|
||||
e.network.raw("NICK %s" % e.network.nicks[0])
|
||||
e.network.raw("USER %s %s %s :%s" %
|
||||
(e.network.username, "8", "*", e.network.fullname))
|
||||
#per rfc2812 these are username, user mode flags, unused, realname
|
||||
|
||||
#e.network.me = None
|
||||
e.done = True
|
||||
|
||||
def onDisconnect(e):
|
||||
if hasattr(e.network,'_reconnect_source'):
|
||||
e.network._reconnect_source.unregister()
|
||||
del e.network._reconnect_source
|
||||
if hasattr(e.network,'connect_timestamp'):
|
||||
if e.error and conf.get('autoreconnect',True):
|
||||
delay = time.time() - e.network.connect_timestamp > 30 and 30 or 120
|
||||
def do_reconnect():
|
||||
if not e.network.status:
|
||||
server(network=e.network)
|
||||
def do_announce_reconnect():
|
||||
if not e.network.status:
|
||||
windows.get_default(e.network).write("* Will reconnect in %s seconds.." % delay)
|
||||
e.network._reconnect_source = ui.register_timer(delay*1000,do_reconnect)
|
||||
e.network._reconnect_source = ui.register_idle(do_announce_reconnect)
|
||||
|
||||
def onCloseNetwork(e):
|
||||
e.network.quit()
|
||||
if hasattr(e.network,'_reconnect_source'):
|
||||
e.network._reconnect_source.unregister()
|
||||
del e.network._reconnect_source
|
||||
|
||||
def setdownDisconnect(e):
|
||||
if hasattr(e.network,'connect_timestamp'):
|
||||
del e.network.connect_timestamp
|
||||
|
||||
def setupInput(e):
|
||||
if not e.done:
|
||||
if e.text.startswith(COMMAND_PREFIX) and not e.ctrl:
|
||||
command = e.text[len(COMMAND_PREFIX):]
|
||||
else:
|
||||
command = 'say - %s' % e.text
|
||||
|
||||
core.events.run(command, e.window, e.network)
|
||||
|
||||
e.done = True
|
||||
|
||||
def onCommandSay(e):
|
||||
if isinstance(e.window, windows.ChannelWindow) or isinstance(e.window, windows.QueryWindow):
|
||||
e.network.msg(e.window.id, ' '.join(e.args))
|
||||
else:
|
||||
raise core.events.CommandError("There's no one here to speak to.")
|
||||
|
||||
def onCommandMsg(e):
|
||||
e.network.msg(e.args[0], ' '.join(e.args[1:]))
|
||||
|
||||
def onCommandNotice(e):
|
||||
e.network.notice(e.args[0], ' '.join(e.args[1:]))
|
||||
|
||||
def onCommandQuery(e):
|
||||
windows.new(windows.QueryWindow, e.network, e.args[0], core).activate()
|
||||
if len(e.args) > 1:
|
||||
message = ' '.join(e.args[1:])
|
||||
if message: #this is false if you do "/query nickname "
|
||||
e.network.msg(e.args[0], ' '.join(e.args[1:]))
|
||||
|
||||
def setupJoin(e):
|
||||
if e.source == e.network.me:
|
||||
chan = e.network.norm_case(e.channel)
|
||||
e.network.on_channels.add(chan)
|
||||
e.network.requested_joins.discard(chan)
|
||||
|
||||
def setdownPart(e):
|
||||
if e.source == e.network.me:
|
||||
chan = e.network.norm_case(e.channel)
|
||||
e.network.on_channels.discard(chan)
|
||||
e.network.requested_parts.discard(chan)
|
||||
|
||||
def setdownKick(e):
|
||||
if e.target == e.network.me:
|
||||
chan = e.network.norm_case(e.channel)
|
||||
e.network.on_channels.discard(chan)
|
||||
|
||||
def ischan(network, channel):
|
||||
return network.norm_case(channel) in network.on_channels
|
||||
|
||||
# make /nick work offline
|
||||
def change_nick(network, nick):
|
||||
if not network.status:
|
||||
core.events.trigger(
|
||||
'Nick',
|
||||
network=network, window=windows.get_default(network),
|
||||
source=network.me, target=nick, address='', text=nick
|
||||
)
|
||||
network.nicks[0] = nick
|
||||
network.me = nick
|
||||
else:
|
||||
network.raw('NICK :%s' % nick)
|
||||
|
||||
def onCommandNick(e):
|
||||
default_nick = irc.default_nicks()[0]
|
||||
if 't' not in e.switches and e.network.me == default_nick:
|
||||
conf['nick'] = e.args[0]
|
||||
import conf as _conf
|
||||
_conf.save()
|
||||
for network in set(w.network for w in core.manager):
|
||||
if network.me == default_nick:
|
||||
change_nick(network, e.args[0])
|
||||
else:
|
||||
change_nick(e.network, e.args[0])
|
||||
|
||||
def setdownNick(e):
|
||||
if e.source != e.network.me:
|
||||
window = windows.get(windows.QueryWindow, e.network, e.source)
|
||||
if window:
|
||||
window.id = e.target
|
||||
|
||||
# make /quit always disconnect us
|
||||
def onCommandQuit(e):
|
||||
if e.network.status:
|
||||
e.network.quit(' '.join(e.args))
|
||||
else:
|
||||
raise core.events.CommandError("We're not connected to a network.")
|
||||
|
||||
def onCommandRaw(e):
|
||||
if e.network.status >= irc.INITIALIZING:
|
||||
e.network.raw(' '.join(e.args))
|
||||
else:
|
||||
raise core.events.CommandError("We're not connected to a network.")
|
||||
|
||||
onCommandQuote = onCommandRaw
|
||||
|
||||
def onCommandJoin(e):
|
||||
if e.args:
|
||||
if e.network.status >= irc.INITIALIZING:
|
||||
e.network.join(' '.join(e.args), requested = 'n' not in e.switches)
|
||||
else:
|
||||
raise core.events.CommandError("We're not connected.")
|
||||
elif isinstance(e.window, windows.ChannelWindow):
|
||||
e.window.network.join(e.window.id, requested = 'n' not in e.switches)
|
||||
else:
|
||||
raise core.events.CommandError("You must supply a channel.")
|
||||
|
||||
def onCommandPart(e):
|
||||
if e.args:
|
||||
if e.network.status >= irc.INITIALIZING:
|
||||
e.network.part(' '.join(e.args), requested = 'n' not in e.switches)
|
||||
else:
|
||||
raise core.events.CommandError("We're not connected.")
|
||||
elif isinstance(e.window, windows.ChannelWindow):
|
||||
e.window.network.part(e.window.id, requested = 'n' not in e.switches)
|
||||
else:
|
||||
raise core.events.CommandError("You must supply a channel.")
|
||||
|
||||
def onCommandHop(e):
|
||||
if e.args:
|
||||
if e.network.status >= irc.INITIALIZING:
|
||||
e.network.part(e.args[0], requested = False)
|
||||
e.network.join(' '.join(e.args), requested = False)
|
||||
else:
|
||||
raise core.events.CommandError("We're not connected.")
|
||||
elif isinstance(e.window, windows.ChannelWindow):
|
||||
e.window.network.part(e.window.id, requested = False)
|
||||
e.window.network.join(e.window.id, requested = False)
|
||||
else:
|
||||
raise core.events.CommandError("You must supply a channel.")
|
||||
|
||||
#this should be used whereever a new irc.Network may need to be created
|
||||
def server(server=None,port=6667,network=None,connect=True):
|
||||
network_info = {}
|
||||
|
||||
if server:
|
||||
network_info["name"] = server
|
||||
network_info["server"] = server
|
||||
if port:
|
||||
network_info["port"] = port
|
||||
get_network_info(server, network_info)
|
||||
|
||||
if not network:
|
||||
network = irc.Network(**network_info)
|
||||
windows.new(windows.StatusWindow, network, "status").activate()
|
||||
else:
|
||||
if "server" in network_info:
|
||||
network.name = network_info['name']
|
||||
network.server = network_info['server']
|
||||
if not network.status:
|
||||
#window = windows.get_default(network)
|
||||
window = core.window
|
||||
if window:
|
||||
window.update()
|
||||
if "port" in network_info:
|
||||
network.port = network_info["port"]
|
||||
|
||||
if network.status:
|
||||
network.quit()
|
||||
if connect:
|
||||
network.connect()
|
||||
core.window.write("* Connecting to %s on port %s" % (network.server, network.port))
|
||||
#windows.get_default(network).write(
|
||||
# "* Connecting to %s on port %s" % (network.server, network.port)
|
||||
# )
|
||||
|
||||
return network
|
||||
|
||||
def onCommandServer(e):
|
||||
host = port = None
|
||||
|
||||
if e.args:
|
||||
host = e.args[0]
|
||||
|
||||
if ':' in host:
|
||||
host, port = host.rsplit(':', 1)
|
||||
port = int(port)
|
||||
|
||||
elif len(e.args) > 1:
|
||||
port = int(e.args[1])
|
||||
|
||||
else:
|
||||
port = 6667
|
||||
|
||||
if 'm' in e.switches:
|
||||
network = None
|
||||
else:
|
||||
network = e.network
|
||||
|
||||
server(server=host, port=port, network=network, connect='o' not in e.switches)
|
||||
|
||||
#see http://www.w3.org/Addressing/draft-mirashi-url-irc-01.txt
|
||||
def onCommandIrcurl(e):
|
||||
url = e.args[0]
|
||||
|
||||
if url.startswith('irc://'):
|
||||
url = url[6:]
|
||||
|
||||
if not url.startswith('/'):
|
||||
host, target = url.rsplit('/',1)
|
||||
if ':' in host:
|
||||
host, port = host.rsplit(':',1)
|
||||
else:
|
||||
port = 6667
|
||||
else:
|
||||
host = None
|
||||
port = 6667
|
||||
target = url
|
||||
|
||||
if host:
|
||||
if e.network and e.network.server == host:
|
||||
network = e.network
|
||||
else:
|
||||
for w in list(windows.manager):
|
||||
if w.network and w.network.server == host:
|
||||
network = w.network
|
||||
break
|
||||
else:
|
||||
for w in list(windows.manager):
|
||||
if w.network and w.network.server == 'irc.default.org':
|
||||
network = server(host,port,w.network)
|
||||
break
|
||||
else:
|
||||
network = server(host,port)
|
||||
|
||||
if ',' in target:
|
||||
target, modifiers = target.split(',',1)
|
||||
action = ''
|
||||
else:
|
||||
target = unquote(target)
|
||||
if target[0] not in '#&+':
|
||||
target = '#'+target
|
||||
action = 'join %s' % target
|
||||
|
||||
if network.status == irc.CONNECTED:
|
||||
core.events.run(action, windows.get_default(network), network)
|
||||
else:
|
||||
if not hasattr(network,'temp_perform'):
|
||||
network.temp_perform = [action]
|
||||
else:
|
||||
network.temp_perform.append(action)
|
||||
|
||||
#commands that we need to add a : to but otherwise can send unchanged
|
||||
#the dictionary contains the number of arguments we take without adding the :
|
||||
trailing = {
|
||||
'away':0,
|
||||
'cnotice':2,
|
||||
'cprivmsg':2,
|
||||
'kick':2,
|
||||
'kill':1,
|
||||
'part':1,
|
||||
'squery':1,
|
||||
'squit':1,
|
||||
'topic':1,
|
||||
'wallops':0,
|
||||
}
|
||||
|
||||
needschan = {
|
||||
'topic':0,
|
||||
'invite':1,
|
||||
'kick':0,
|
||||
# 'mode':0, #this is commonly used for channels, but can apply to users
|
||||
# 'names':0, #with no parameters, this is supposed to give a list of all users; we may be able to safely ignore that.
|
||||
}
|
||||
|
||||
def setupCommand(e):
|
||||
if not e.done:
|
||||
if e.name in needschan and isinstance(e.window, windows.ChannelWindow):
|
||||
valid_chan_prefixes = e.network.isupport.get('CHANTYPES', '#&+')
|
||||
chan_pos = needschan[e.name]
|
||||
|
||||
if len(e.args) > chan_pos:
|
||||
if not e.args[chan_pos] or e.args[chan_pos][0] not in valid_chan_prefixes:
|
||||
e.args.insert(chan_pos, e.window.id)
|
||||
else:
|
||||
e.args.append(e.window.id)
|
||||
|
||||
if e.name in trailing:
|
||||
trailing_pos = trailing[e.name]
|
||||
|
||||
if len(e.args) > trailing_pos:
|
||||
e.args[trailing_pos] = ':%s' % e.args[trailing_pos]
|
||||
|
||||
e.text = '%s %s' % (e.name, ' '.join(e.args))
|
||||
|
||||
def setdownCommand(e):
|
||||
if not e.done and e.network.status >= irc.INITIALIZING:
|
||||
e.network.raw(e.text)
|
||||
e.done = True
|
||||
|
||||
def get_network_info(name, network_info):
|
||||
conf_info = conf.get('networks', {}).get(name)
|
||||
|
||||
if conf_info:
|
||||
network_info['server'] = name
|
||||
network_info.update(conf_info)
|
||||
|
||||
def onStart(e):
|
||||
for network in conf.get('start_networks', []):
|
||||
server(server=network)
|
||||
|
||||
def onConnect(e):
|
||||
network_info = conf.get('networks', {}).get(e.network.name, {})
|
||||
|
||||
for command in network_info.get('perform', []):
|
||||
while command.startswith(COMMAND_PREFIX):
|
||||
command = command[len(COMMAND_PREFIX):]
|
||||
core.events.run(command, e.window, e.network)
|
||||
|
||||
tojoin = ','.join(network_info.get('join', []))
|
||||
if tojoin:
|
||||
core.events.run('join -n %s' % tojoin, e.window, e.network)
|
||||
|
||||
if hasattr(e.network,'temp_perform'):
|
||||
for command in e.network.temp_perform:
|
||||
core.events.run(command, e.window, e.network)
|
||||
del e.network.temp_perform
|
||||
|
||||
def isautojoin(network, channel):
|
||||
try:
|
||||
joinlist = conf['networks'][network.name]['join']
|
||||
except KeyError:
|
||||
return False
|
||||
normchannel = network.norm_case(channel)
|
||||
for chan in joinlist:
|
||||
if normchannel == network.norm_case(chan):
|
||||
return True
|
||||
return False
|
||||
|
||||
def setautojoin(network, channel):
|
||||
if 'networks' not in conf:
|
||||
conf['networks'] = networks = {}
|
||||
else:
|
||||
networks = conf['networks']
|
||||
if network.name not in networks:
|
||||
networks[network.name] = network_settings = {}
|
||||
if 'start_networks' not in conf:
|
||||
conf['start_networks'] = []
|
||||
conf['start_networks'].append(network.name)
|
||||
else:
|
||||
network_settings = networks[network.name]
|
||||
|
||||
if 'join' not in network_settings:
|
||||
network_settings['join'] = [channel]
|
||||
else:
|
||||
network_settings['join'].append(channel)
|
||||
|
||||
def unsetautojoin(network, channel):
|
||||
try:
|
||||
joinlist = conf['networks'][network.name]['join']
|
||||
except KeyError:
|
||||
return False
|
||||
normchannel = network.norm_case(channel)
|
||||
for i, chan in enumerate(joinlist[:]):
|
||||
if normchannel == network.norm_case(chan):
|
||||
joinlist.pop(i)
|
||||
|
||||
def onChannelMenu(e):
|
||||
def toggle_join():
|
||||
if isautojoin(e.network, e.channel):
|
||||
unsetautojoin(e.network, e.channel)
|
||||
else:
|
||||
setautojoin(e.network, e.channel)
|
||||
|
||||
e.menu.append(('Autojoin', isautojoin(e.network, e.channel), toggle_join))
|
70
services/console/lib/purk/scripts/keys.py
Normal file
70
services/console/lib/purk/scripts/keys.py
Normal file
@ -0,0 +1,70 @@
|
||||
import windows
|
||||
import widgets
|
||||
import irc
|
||||
|
||||
shortcuts = {
|
||||
'^b': '\x02',
|
||||
'^u': '\x1F',
|
||||
'^r': '\x16',
|
||||
'^k': '\x03',
|
||||
'^l': '\x04',
|
||||
'^o': '\x0F',
|
||||
}
|
||||
|
||||
def onKeyPress(e):
|
||||
if e.key in shortcuts:
|
||||
e.window.input.insert(shortcuts[e.key])
|
||||
|
||||
elif e.key == '!c':
|
||||
e.window.output.copy()
|
||||
|
||||
elif e.key == 'Page_Up':
|
||||
e.window.output.y = e.window.output.y - e.window.output.height / 2
|
||||
|
||||
elif e.key == 'Page_Down':
|
||||
e.window.output.y = e.window.output.y + e.window.output.height / 2
|
||||
|
||||
elif e.key == '^Home':
|
||||
e.window.output.y = 0
|
||||
|
||||
elif e.key == '^End':
|
||||
e.window.output.y = e.window.output.ymax
|
||||
|
||||
elif e.key in ('^Page_Up', '^Page_Down'):
|
||||
winlist = list(windows.manager)
|
||||
index = winlist.index(e.window) + ((e.key == '^Page_Down') and 1 or -1)
|
||||
if 0 <= index < len(winlist):
|
||||
winlist[index].activate()
|
||||
|
||||
elif e.key == '!a':
|
||||
winlist = list(windows.manager)
|
||||
winlist = winlist[winlist.index(e.window):]+winlist
|
||||
w = [w for w in winlist if widgets.HILIT in w.activity]
|
||||
|
||||
if not w:
|
||||
w = [w for w in winlist if widgets.TEXT in w.activity]
|
||||
|
||||
if w:
|
||||
windows.manager.set_active(w[0])
|
||||
|
||||
# tabbed browsing
|
||||
elif e.key == '^t':
|
||||
windows.new(windows.StatusWindow, irc.Network(), 'status').activate()
|
||||
|
||||
elif e.key == '^w':
|
||||
windows.manager.get_active().close()
|
||||
|
||||
elif e.key == '^f':
|
||||
window = windows.manager.get_active()
|
||||
|
||||
find = widgets.FindBox(window)
|
||||
|
||||
window.pack_start(find, expand=False)
|
||||
|
||||
find.textbox.grab_focus()
|
||||
|
||||
elif len(e.key) == 2 and e.key.startswith('!') and e.key[1].isdigit():
|
||||
n = int(e.key[1])
|
||||
if n and n <= len(core.manager):
|
||||
list(core.manager)[n-1].activate()
|
||||
#else e.key == "!0"
|
366
services/console/lib/purk/scripts/theme.py
Normal file
366
services/console/lib/purk/scripts/theme.py
Normal file
@ -0,0 +1,366 @@
|
||||
import time
|
||||
|
||||
import windows
|
||||
import widgets
|
||||
import chaninfo
|
||||
|
||||
from conf import conf
|
||||
|
||||
textareas = {}
|
||||
if 'font' in conf:
|
||||
textareas['font'] = conf['font']
|
||||
if 'bg_color' in conf:
|
||||
textareas['bg'] = conf['bg_color']
|
||||
if 'fg_color' in conf:
|
||||
textareas['fg'] = conf['fg_color']
|
||||
|
||||
widgets.set_style("view", textareas)
|
||||
widgets.set_style("nicklist", textareas)
|
||||
|
||||
#copied pretty directly from something that was probably copied from wine sources
|
||||
def RGBtoHSL(r, g, b):
|
||||
maxval = max(r, g, b)
|
||||
minval = min(r, g, b)
|
||||
|
||||
luminosity = ((maxval + minval) * 240 + 255) // 510
|
||||
|
||||
if maxval == minval:
|
||||
saturation = 0
|
||||
hue = 160
|
||||
else:
|
||||
delta = maxval - minval
|
||||
|
||||
if luminosity <= 120:
|
||||
saturation = ((maxval+minval)//2 + delta*240) // (maxval + minval)
|
||||
else:
|
||||
saturation = ((150-maxval-minval)//2 + delta*240) // (150-maxval-minval)
|
||||
|
||||
#sigh..
|
||||
rnorm = (delta//2 + maxval*40 - r*40)//delta
|
||||
gnorm = (delta//2 + maxval*40 - g*40)//delta
|
||||
bnorm = (delta//2 + maxval*40 - b*40)//delta
|
||||
|
||||
if r == maxval:
|
||||
hue = bnorm-gnorm
|
||||
elif g == maxval:
|
||||
hue = 80+rnorm-bnorm
|
||||
else:
|
||||
hue = 160+gnorm-rnorm
|
||||
hue = hue % 240
|
||||
return hue, saturation, luminosity
|
||||
|
||||
#copied from the same place
|
||||
def huetoRGB(hue, mid1, mid2):
|
||||
hue = hue % 240
|
||||
|
||||
if hue > 160:
|
||||
return mid1
|
||||
elif hue > 120:
|
||||
hue = 160 - hue
|
||||
elif hue > 40:
|
||||
return mid2
|
||||
return ((hue * (mid2 - mid1) + 20) // 40) + mid1
|
||||
|
||||
#this too
|
||||
def HSLtoRGB(hue, saturation, luminosity):
|
||||
if saturation != 0:
|
||||
if luminosity > 120:
|
||||
mid2 = saturation + luminosity - (saturation * luminosity + 120)//240
|
||||
else:
|
||||
mid2 = ((saturation + 240) * luminosity + 120)//240
|
||||
|
||||
mid1 = luminosity * 2 - mid2
|
||||
|
||||
return tuple((huetoRGB(hue+x, mid1, mid2) * 255 + 120) // 240 for x in (80,0,-80))
|
||||
else:
|
||||
value = luminosity * 255 // 240
|
||||
return value, value, value
|
||||
|
||||
def gethashcolor(string):
|
||||
h = hash(string)
|
||||
rgb = HSLtoRGB(h%241, 100-h//241%61, 90)
|
||||
return "%02x%02x%02x" % rgb
|
||||
|
||||
#take an event e and trigger the highlight event if necessary
|
||||
def hilight_text(e):
|
||||
if not hasattr(e, 'Highlight'):
|
||||
e.Highlight = []
|
||||
core.events.trigger('Highlight', e)
|
||||
|
||||
#hilight own nick
|
||||
def onHighlight(e):
|
||||
lowertext = e.text.lower()
|
||||
for word in conf.get('highlight_words', []) + [e.network.me] + e.network.nicks:
|
||||
lowerword = word.lower()
|
||||
pos = lowertext.find(lowerword, 0)
|
||||
while pos != -1:
|
||||
e.Highlight.append((pos, pos+len(word)))
|
||||
pos = lowertext.find(lowerword, pos+1)
|
||||
|
||||
def prefix(e):
|
||||
return time.strftime(conf.get('timestamp', ''))
|
||||
|
||||
def getsourcecolor(e):
|
||||
address = getattr(e, "address", "")
|
||||
if address:
|
||||
if e.network.me == e.source:
|
||||
e.network._my_address = address
|
||||
elif e.network.me == e.source:
|
||||
address = getattr(e.network, "_my_address", "")
|
||||
if '@' in address:
|
||||
address = address.split('@')[1]
|
||||
if not address:
|
||||
address = e.source
|
||||
return "\x04%s" % gethashcolor(address)
|
||||
|
||||
def format_source(e):
|
||||
highlight = getattr(e, "Highlight", "") and '\x02' or ''
|
||||
return "%s\x04%s%s" % (highlight, getsourcecolor(e), e.source)
|
||||
|
||||
def format_info_source(e):
|
||||
if e.source == e.network.me:
|
||||
return "\x04%sYou" % (getsourcecolor(e))
|
||||
else:
|
||||
return "\x04%s%s" % (getsourcecolor(e), e.source)
|
||||
|
||||
def address(e):
|
||||
#if e.source != e.network.me:
|
||||
# return "%s " % info_in_brackets(e.address)
|
||||
#else:
|
||||
# return ""
|
||||
return ""
|
||||
|
||||
def text(e):
|
||||
if e.text:
|
||||
#return " %s" % info_in_brackets(e.text)
|
||||
return ": \x0F%s" % e.text
|
||||
else:
|
||||
return ""
|
||||
|
||||
def info_in_brackets(text):
|
||||
return "(\x044881b6%s\x0F)" % text
|
||||
|
||||
def pretty_time(secs):
|
||||
times = (
|
||||
#("years", "year", 31556952),
|
||||
("weeks", "week", 604800),
|
||||
("days", "day", 86400),
|
||||
("hours", "hour", 3600),
|
||||
("minutes", "minute", 60),
|
||||
("seconds", "second", 1),
|
||||
)
|
||||
if secs == 0:
|
||||
return "0 seconds"
|
||||
result = ""
|
||||
for plural, singular, amount in times:
|
||||
n, secs = divmod(secs, amount)
|
||||
if n == 1:
|
||||
result = result + " %s %s" % (n, singular)
|
||||
elif n:
|
||||
result = result + " %s %s" % (n, plural)
|
||||
return result[1:]
|
||||
|
||||
def onText(e):
|
||||
hilight_text(e)
|
||||
color = getsourcecolor(e)
|
||||
to_write = prefix(e)
|
||||
if e.network.me == e.target: # this is a pm
|
||||
if e.window.id == e.network.norm_case(e.source):
|
||||
to_write += "\x02<\x0F%s\x0F\x02>\x0F " % (format_source(e))
|
||||
else:
|
||||
to_write += "\x02*\x0F%s\x0F\x02*\x0F " % (format_source(e))
|
||||
else:
|
||||
if e.window.id == e.network.norm_case(e.target):
|
||||
to_write += "\x02<\x0F%s\x0F\x02>\x0F " % (format_source(e))
|
||||
else:
|
||||
to_write += "\x02<\x0F%s:%s\x0F\x02>\x0F " % (format_source(e), e.target)
|
||||
to_write += e.text
|
||||
|
||||
if e.Highlight:
|
||||
e.window.write(to_write, widgets.HILIT)
|
||||
else:
|
||||
e.window.write(to_write, widgets.TEXT)
|
||||
|
||||
def onOwnText(e):
|
||||
color = getsourcecolor(e)
|
||||
to_write = prefix(e)
|
||||
if e.window.id == e.network.norm_case(e.target):
|
||||
to_write += "\x02<\x0F%s\x0F\x02>\x0F %s" % (format_source(e), e.text)
|
||||
else:
|
||||
to_write += "%s->\x0F \x02*\x0F%s\x0F\x02*\x0F %s" % (color, e.target, e.text)
|
||||
|
||||
e.window.write(to_write)
|
||||
|
||||
def onAction(e):
|
||||
hilight_text(e)
|
||||
color = color = getsourcecolor(e)
|
||||
to_write = "%s\x02*\x0F %s\x0F %s" % (prefix(e), format_source(e), e.text)
|
||||
|
||||
if e.Highlight:
|
||||
e.window.write(to_write, widgets.HILIT)
|
||||
else:
|
||||
e.window.write(to_write, widgets.TEXT)
|
||||
|
||||
def onOwnAction(e):
|
||||
color = getsourcecolor(e)
|
||||
to_write = "%s\x02*\x0F %s\x0F %s" % (prefix(e), format_source(e), e.text)
|
||||
|
||||
e.window.write(to_write)
|
||||
|
||||
def onNotice(e):
|
||||
hilight_text(e)
|
||||
color = getsourcecolor(e)
|
||||
to_write = prefix(e)
|
||||
if e.network.me == e.target: # this is a pm
|
||||
to_write += "\x02-\x0F%s\x0F\x02-\x0F " % (format_source(e))
|
||||
else:
|
||||
to_write += "\x02-\x0F%s:%s\x0F\x02-\x0F " % (format_source(e), e.target)
|
||||
to_write += e.text
|
||||
|
||||
e.window.write(to_write, (e.Highlight and widgets.HILIT) or widgets.TEXT)
|
||||
|
||||
def onOwnNotice(e):
|
||||
color = getsourcecolor(e)
|
||||
to_write = "%s-> \x02-\x02%s\x0F\x02-\x0F %s" % (prefix(e), e.target, e.text)
|
||||
|
||||
e.window.write(to_write)
|
||||
|
||||
def onCtcp(e):
|
||||
color = getsourcecolor(e)
|
||||
to_write = "%s\x02[\x02%s\x0F\x02]\x0F %s" % (prefix(e), format_source(e), e.text)
|
||||
|
||||
if not e.quiet:
|
||||
e.window.write(to_write)
|
||||
|
||||
def onCtcpReply(e):
|
||||
color = getsourcecolor(e)
|
||||
to_write = "%s%s--- %s reply from %s:\x0F %s" % (prefix(e), color, e.name.capitalize(), format_source(e), ' '.join(e.args))
|
||||
|
||||
window = windows.manager.get_active()
|
||||
if window.network != e.network:
|
||||
window = windows.get_default(e.network)
|
||||
window.write(to_write, widgets.TEXT)
|
||||
|
||||
def onJoin(e):
|
||||
if e.source == e.network.me:
|
||||
to_write = "%s%s %sjoin %s" % (prefix(e), format_info_source(e), address(e), e.target)
|
||||
else:
|
||||
to_write = "%s%s %sjoins %s" % (prefix(e), format_info_source(e), address(e), e.target)
|
||||
|
||||
e.window.write(to_write)
|
||||
|
||||
def onPart(e):
|
||||
if e.source == e.network.me:
|
||||
to_write = "%s%s leave %s%s" % (prefix(e), format_info_source(e), e.target, text(e))
|
||||
else:
|
||||
to_write = "%s%s leaves %s%s" % (prefix(e), format_info_source(e), e.target, text(e))
|
||||
|
||||
e.window.write(to_write)
|
||||
|
||||
def onKick(e):
|
||||
if e.source == e.network.me:
|
||||
to_write = "%s%s kick %s%s" % (prefix(e), format_info_source(e), e.target, text(e))
|
||||
else:
|
||||
to_write = "%s%s kicks %s%s" % (prefix(e), format_info_source(e), e.target, text(e))
|
||||
|
||||
e.window.write(to_write, (e.target == e.network.me and widgets.HILIT) or widgets.EVENT)
|
||||
|
||||
def onMode(e):
|
||||
if e.source == e.network.me:
|
||||
to_write = "%s%s set mode:\x0F %s" % (prefix(e), format_info_source(e), e.text)
|
||||
else:
|
||||
to_write = "%s%s sets mode:\x0F %s" % (prefix(e), format_info_source(e), e.text)
|
||||
|
||||
e.window.write(to_write)
|
||||
|
||||
def onQuit(e):
|
||||
to_write = "%s%s leaves%s" % (prefix(e), format_info_source(e), text(e))
|
||||
|
||||
for channame in chaninfo.channels(e.network):
|
||||
if chaninfo.ison(e.network, channame, e.source):
|
||||
window = windows.get(windows.ChannelWindow, e.network, channame, core)
|
||||
if window:
|
||||
window.write(to_write)
|
||||
|
||||
def onNick(e):
|
||||
color = getsourcecolor(e)
|
||||
if e.source == e.network.me:
|
||||
to_write = "%s%sYou are now known as %s" % (prefix(e), color, e.target)
|
||||
else:
|
||||
to_write = "%s%s%s is now known as %s" % (prefix(e), color, e.source, e.target)
|
||||
|
||||
if e.source == e.network.me:
|
||||
for window in windows.get_with(core.manager, network=e.network):
|
||||
window.write(to_write)
|
||||
else:
|
||||
for channame in chaninfo.channels(e.network):
|
||||
if chaninfo.ison(e.network,channame,e.source):
|
||||
window = windows.get(windows.ChannelWindow, e.network, channame)
|
||||
if window:
|
||||
window.write(to_write)
|
||||
|
||||
def onTopic(e):
|
||||
if e.source == e.network.me:
|
||||
to_write = "%s%s set topic:\x0F %s" % (prefix(e), format_info_source(e), e.text)
|
||||
else:
|
||||
to_write = "%s%s sets topic:\x0F %s" % (prefix(e), format_info_source(e), e.text)
|
||||
|
||||
e.window.write(to_write)
|
||||
|
||||
def onRaw(e):
|
||||
if not e.quiet:
|
||||
if e.msg[1].isdigit():
|
||||
if e.msg[1] == '332':
|
||||
window = windows.get(windows.ChannelWindow, e.network, e.msg[3], core) or e.window
|
||||
window.write(
|
||||
"%sTopic on %s is: %s" %
|
||||
(prefix(e), e.msg[3], e.text)
|
||||
)
|
||||
|
||||
elif e.msg[1] == '333':
|
||||
window = windows.get(windows.ChannelWindow, e.network, e.msg[3], core) or e.window
|
||||
window.write(
|
||||
"%sTopic on %s set by %s at time %s" %
|
||||
(prefix(e), e.msg[3], e.msg[4], time.ctime(int(e.msg[5])))
|
||||
)
|
||||
|
||||
elif e.msg[1] == '329': #RPL_CREATIONTIME
|
||||
pass
|
||||
|
||||
elif e.msg[1] == '311': #RPL_WHOISUSER
|
||||
e.window.write("* %s is %s@%s * %s" % (e.msg[3], e.msg[4], e.msg[5], e.msg[7]))
|
||||
|
||||
elif e.msg[1] == '312': #RPL_WHOISSERVER
|
||||
e.window.write("* %s on %s (%s)" % (e.msg[3], e.msg[4], e.msg[5]))
|
||||
|
||||
elif e.msg[1] == '317': #RPL_WHOISIDLE
|
||||
e.window.write("* %s has been idle for %s" % (e.msg[3], pretty_time(int(e.msg[4]))))
|
||||
if e.msg[5].isdigit():
|
||||
e.window.write("* %s signed on %s" % (e.msg[3], time.ctime(int(e.msg[5]))))
|
||||
|
||||
elif e.msg[1] == '319': #RPL_WHOISCHANNELS
|
||||
e.window.write("* %s on channels: %s" % (e.msg[3], e.msg[4]))
|
||||
|
||||
elif e.msg[1] == '330': #RPL_WHOISACCOUNT
|
||||
#this appears to conflict with another raw, so if there's anything weird about it,
|
||||
# we fall back on the default
|
||||
if len(e.msg) == 6 and not e.msg[4].isdigit() and not e.msg[5].isdigit():
|
||||
e.window.write("* %s %s %s" % (e.msg[3], e.msg[5], e.msg[4]))
|
||||
else:
|
||||
e.window.write("* %s" % ' '.join(e.msg[3:]))
|
||||
|
||||
else:
|
||||
e.window.write("* %s" % ' '.join(e.msg[3:]))
|
||||
elif e.msg[1] == 'ERROR':
|
||||
e.window.write("Error: %s" % e.text)
|
||||
|
||||
def onDisconnect(e):
|
||||
to_write = '%s* Disconnected' % prefix(e)
|
||||
if e.error:
|
||||
to_write += ' (%s)' % e.error
|
||||
|
||||
for window in windows.get_with(network=e.network):
|
||||
if isinstance(window, windows.StatusWindow):
|
||||
window.write(to_write, widgets.TEXT)
|
||||
else:
|
||||
window.write(to_write, widgets.EVENT)
|
45
services/console/lib/purk/scripts/timeout.py
Executable file
45
services/console/lib/purk/scripts/timeout.py
Executable file
@ -0,0 +1,45 @@
|
||||
import time
|
||||
|
||||
import ui
|
||||
from conf import conf
|
||||
|
||||
def setupRaw(e):
|
||||
e.network._message_timeout = False
|
||||
|
||||
def onSocketConnect(e):
|
||||
timeout = conf.get("server_traffic_timeout", 120)*1000
|
||||
e.network._message_timeout = False
|
||||
if timeout:
|
||||
e.network._message_timeout_source = ui.register_timer(timeout, check_timeout, e.network)
|
||||
else:
|
||||
e.network._message_timeout_source = None
|
||||
|
||||
def check_timeout(network):
|
||||
if network._message_timeout:
|
||||
network.raw("PING %s" % network.me)
|
||||
timeout = conf.get("server_death_timeout", 240)*1000
|
||||
network._message_timeout_source = ui.register_timer(timeout, check_death_timeout, network)
|
||||
return False
|
||||
else:
|
||||
network._message_timeout = True
|
||||
return True # call this function again
|
||||
|
||||
def check_death_timeout(network):
|
||||
if network._message_timeout:
|
||||
network.raw("QUIT :Server missing, presumed dead")
|
||||
network.disconnect(error="The server seems to have stopped talking to us")
|
||||
else:
|
||||
network._message_timeout = False
|
||||
timeout = conf.get("server_traffic_timeout", 120)*1000
|
||||
if timeout:
|
||||
network._message_timeout_source = ui.register_timer(timeout, check_timeout, network)
|
||||
else:
|
||||
network._message_timeout_source = None
|
||||
|
||||
def onDisconnect(e):
|
||||
try:
|
||||
if e.network._message_timeout_source:
|
||||
e.network._message_timeout_source.unregister()
|
||||
e.network._message_timeout_source = None
|
||||
except AttributeError:
|
||||
pass
|
132
services/console/lib/purk/scripts/ui_script.py
Normal file
132
services/console/lib/purk/scripts/ui_script.py
Normal file
@ -0,0 +1,132 @@
|
||||
import irc
|
||||
import ui
|
||||
import windows
|
||||
import irc_script
|
||||
from conf import conf
|
||||
|
||||
# FIXME: meh still might want rid of these, I'm not sure yet
|
||||
|
||||
def onActive(e):
|
||||
e.window.activity = None
|
||||
|
||||
ui.register_idle(windows.manager.set_title)
|
||||
|
||||
def setupNick(e):
|
||||
if e.source == e.network.me:
|
||||
for w in windows.get_with(core.manager, network=e.network):
|
||||
try:
|
||||
w.nick_label.update(e.target)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def onExit(e):
|
||||
for n in set(w.network for w in windows.manager):
|
||||
if n:
|
||||
n.quit()
|
||||
|
||||
def setupJoin(e):
|
||||
if e.source == e.network.me:
|
||||
window = windows.get(windows.StatusWindow, e.network, 'status', core)
|
||||
|
||||
if window and not conf.get('status'):
|
||||
window.mutate(windows.ChannelWindow, e.network, e.target)
|
||||
else:
|
||||
window = windows.new(windows.ChannelWindow, e.network, e.target, core)
|
||||
|
||||
if e.requested:
|
||||
window.activate()
|
||||
|
||||
e.window = windows.get(windows.ChannelWindow, e.network, e.target, core) or e.window
|
||||
|
||||
def setupText(e):
|
||||
if e.target == e.network.me:
|
||||
e.window = windows.new(windows.QueryWindow, e.network, e.source, core)
|
||||
else:
|
||||
e.window = \
|
||||
windows.get(windows.ChannelWindow, e.network, e.target, core) or \
|
||||
windows.get(windows.QueryWindow, e.network, e.source, core) or \
|
||||
e.window
|
||||
|
||||
setupAction = setupText
|
||||
|
||||
def setupNotice(e):
|
||||
if e.target != e.network.me:
|
||||
e.window = \
|
||||
windows.get(windows.ChannelWindow, e.network, e.target, core) or e.window
|
||||
|
||||
def setupOwnText(e):
|
||||
e.window = \
|
||||
windows.get(windows.ChannelWindow, e.network, e.target, core) or \
|
||||
windows.get(windows.QueryWindow, e.network, e.target, core) or \
|
||||
e.window
|
||||
|
||||
setupOwnAction = setupOwnText
|
||||
|
||||
def setdownPart(e):
|
||||
if e.source == e.network.me:
|
||||
window = windows.get(windows.ChannelWindow, e.network, e.target, core)
|
||||
|
||||
if window:
|
||||
cwindows = list(windows.get_with(
|
||||
network=window.network,
|
||||
wclass=windows.ChannelWindow
|
||||
))
|
||||
|
||||
if len(cwindows) == 1 and not list(windows.get_with(network=window.network, wclass=windows.StatusWindow)):
|
||||
window.mutate(windows.StatusWindow, e.network, 'status')
|
||||
if e.requested:
|
||||
window.activate()
|
||||
elif e.requested:
|
||||
window.close()
|
||||
|
||||
def onClose(e):
|
||||
nwindows = list(windows.get_with(core.manager, network=e.window.network))
|
||||
|
||||
if isinstance(e.window, windows.ChannelWindow):
|
||||
cwindows = list(windows.get_with(core.manager,
|
||||
network=e.window.network,
|
||||
wclass=windows.ChannelWindow
|
||||
))
|
||||
|
||||
#if we only have one window for the network, don't bother to part as
|
||||
# we'll soon be quitting anyway
|
||||
if len(nwindows) != 1 and irc_script.ischan(e.window.network, e.window.id):
|
||||
e.window.network.part(e.window.id)
|
||||
|
||||
if len(nwindows) == 1:
|
||||
core.events.trigger("CloseNetwork", window=e.window, network=e.window.network)
|
||||
|
||||
elif isinstance(e.window, windows.StatusWindow) and conf.get('status'):
|
||||
core.events.trigger("CloseNetwork", window=e.window, network=e.window.network)
|
||||
for window in nwindows:
|
||||
if window != e.window:
|
||||
window.close()
|
||||
|
||||
if len(core.manager) == 1:
|
||||
windows.new(windows.StatusWindow, irc.Network(), "status", core)
|
||||
|
||||
def onConnecting(e):
|
||||
return
|
||||
window = windows.get_default(e.network)
|
||||
if window:
|
||||
window.update()
|
||||
|
||||
onDisconnect = onConnecting
|
||||
|
||||
def setupPart(e):
|
||||
e.window = windows.get(windows.ChannelWindow, e.network, e.target, core) or e.window
|
||||
|
||||
setupTopic = setupPart
|
||||
|
||||
def setupKick(e):
|
||||
e.window = windows.get(windows.ChannelWindow, e.network, e.channel, core) or e.window
|
||||
|
||||
def setupMode(e):
|
||||
if e.target != e.network.me:
|
||||
e.window = windows.get(windows.ChannelWindow, e.network, e.target, core) or e.window
|
||||
|
||||
def onWindowMenu(e):
|
||||
if isinstance(e.window, windows.ChannelWindow):
|
||||
e.channel = e.window.id
|
||||
e.network = e.window.network
|
||||
core.events.trigger('ChannelMenu', e)
|
51
services/console/lib/purk/servers.py
Normal file
51
services/console/lib/purk/servers.py
Normal file
@ -0,0 +1,51 @@
|
||||
import gtk
|
||||
|
||||
import windows
|
||||
from conf import conf
|
||||
|
||||
if 'networks' not in conf:
|
||||
conf['networks'] = {}
|
||||
|
||||
def server_get_data(network_info):
|
||||
if 'port' in network_info:
|
||||
return "%s:%s" % (
|
||||
network_info.get('server', '') , network_info.get('port')
|
||||
)
|
||||
else:
|
||||
return network_info.get('server', '')
|
||||
|
||||
def server_set_data(text, network_info):
|
||||
if ':' in text:
|
||||
network_info['server'], port = text.rsplit(':',1)
|
||||
network_info['port'] = int(port)
|
||||
else:
|
||||
network_info['server'] = text
|
||||
network_info.pop('port', None)
|
||||
|
||||
def channels_get_data(network_info):
|
||||
return '\n'.join(network_info.get('join', ()))
|
||||
|
||||
def channels_set_data(text, network_info):
|
||||
network_info['join'] = []
|
||||
|
||||
for line in text.split('\n'):
|
||||
for chan in line.split(','):
|
||||
if chan:
|
||||
network_info['join'].append(chan.strip())
|
||||
|
||||
def perform_get_data(network_info):
|
||||
return '\n'.join(network_info.get('perform', ()))
|
||||
|
||||
def perform_set_data(text, network_info):
|
||||
network_info['perform'] = [line for line in text.split('\n') if line]
|
||||
|
||||
def autoconnect_set_data(do_autoconnect, network):
|
||||
if 'start_networks' not in conf:
|
||||
conf['start_networks'] = []
|
||||
|
||||
# note (n in C) != w
|
||||
if (network in conf.get('start_networks')) != do_autoconnect:
|
||||
if do_autoconnect:
|
||||
conf.get('start_networks').append(network)
|
||||
else:
|
||||
conf.get('start_networks').remove(network)
|
105
services/console/lib/purk/ui.py
Normal file
105
services/console/lib/purk/ui.py
Normal file
@ -0,0 +1,105 @@
|
||||
import sys
|
||||
import os
|
||||
import thread
|
||||
import socket
|
||||
import signal
|
||||
import traceback
|
||||
|
||||
import commands
|
||||
|
||||
import gobject
|
||||
|
||||
__sys_path = list(sys.path)
|
||||
import gtk
|
||||
sys.path = __sys_path
|
||||
|
||||
import irc
|
||||
from conf import conf
|
||||
|
||||
import widgets
|
||||
import windows
|
||||
|
||||
# Running from same package dir
|
||||
urkpath = os.path.dirname(__file__)
|
||||
|
||||
def path(filename=""):
|
||||
if filename:
|
||||
return os.path.join(urkpath, filename)
|
||||
else:
|
||||
return urkpath
|
||||
|
||||
# Priority Constants
|
||||
PRIORITY_HIGH = gobject.PRIORITY_HIGH
|
||||
PRIORITY_DEFAULT = gobject.PRIORITY_DEFAULT
|
||||
PRIORITY_HIGH_IDLE = gobject.PRIORITY_HIGH_IDLE
|
||||
PRIORITY_DEFAULT_IDLE = gobject.PRIORITY_DEFAULT_IDLE
|
||||
PRIORITY_LOW = gobject.PRIORITY_LOW
|
||||
|
||||
|
||||
if os.access(path('profile'),os.F_OK) or os.path.expanduser("~") == "~":
|
||||
userpath = path('profile')
|
||||
if not os.access(userpath,os.F_OK):
|
||||
os.mkdir(userpath)
|
||||
if not os.access(os.path.join(userpath,'scripts'),os.F_OK):
|
||||
os.mkdir(os.path.join(userpath,'scripts'))
|
||||
else:
|
||||
userpath = os.path.join(os.path.expanduser("~"), ".urk")
|
||||
if not os.access(userpath,os.F_OK):
|
||||
os.mkdir(userpath, 0700)
|
||||
if not os.access(os.path.join(userpath,'scripts'),os.F_OK):
|
||||
os.mkdir(os.path.join(userpath,'scripts'), 0700)
|
||||
|
||||
|
||||
def set_clipboard(text):
|
||||
gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD).set_text(text)
|
||||
gtk.clipboard_get(gtk.gdk.SELECTION_PRIMARY).set_text(text)
|
||||
|
||||
class Source(object):
|
||||
__slots__ = ['enabled']
|
||||
def __init__(self):
|
||||
self.enabled = True
|
||||
def unregister(self):
|
||||
self.enabled = False
|
||||
|
||||
class GtkSource(object):
|
||||
__slots__ = ['tag']
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
def unregister(self):
|
||||
gobject.source_remove(self.tag)
|
||||
|
||||
def register_idle(f, *args, **kwargs):
|
||||
priority = kwargs.pop("priority",PRIORITY_DEFAULT_IDLE)
|
||||
def callback():
|
||||
return f(*args, **kwargs)
|
||||
return GtkSource(gobject.idle_add(callback, priority=priority))
|
||||
|
||||
def register_timer(time, f, *args, **kwargs):
|
||||
priority = kwargs.pop("priority",PRIORITY_DEFAULT_IDLE)
|
||||
def callback():
|
||||
return f(*args, **kwargs)
|
||||
return GtkSource(gobject.timeout_add(time, callback, priority=priority))
|
||||
|
||||
def fork(cb, f, *args, **kwargs):
|
||||
is_stopped = Source()
|
||||
def thread_func():
|
||||
try:
|
||||
result, error = f(*args, **kwargs), None
|
||||
except Exception, e:
|
||||
result, error = None, e
|
||||
|
||||
if is_stopped.enabled:
|
||||
def callback():
|
||||
if is_stopped.enabled:
|
||||
cb(result, error)
|
||||
|
||||
gobject.idle_add(callback)
|
||||
|
||||
thread.start_new_thread(thread_func, ())
|
||||
return is_stopped
|
||||
|
||||
set_style = widgets.set_style
|
||||
|
||||
def we_get_signal(*what):
|
||||
gobject.idle_add(windows.manager.exit)
|
||||
|
70
services/console/lib/purk/urk_trace.py
Normal file
70
services/console/lib/purk/urk_trace.py
Normal file
@ -0,0 +1,70 @@
|
||||
import sys
|
||||
import commands
|
||||
import linecache
|
||||
|
||||
import time
|
||||
|
||||
last_mem = [0]
|
||||
|
||||
def traceit_memory(frame, event, arg):
|
||||
if event == "line":
|
||||
mem = int(" " + commands.getoutput(
|
||||
"ps -eo cmd,rss | grep urk_trace.py | grep -v grep"
|
||||
).split(" ")[-1])
|
||||
|
||||
if mem > last_mem[0]:
|
||||
last_mem[0] = mem
|
||||
|
||||
mem = str(mem)
|
||||
|
||||
filename = frame.f_globals["__file__"]
|
||||
|
||||
if filename.endswith(".pyc") or filename.endswith(".pyo"):
|
||||
filename = filename[:-1]
|
||||
|
||||
name = frame.f_globals["__name__"]
|
||||
|
||||
lineno = frame.f_lineno
|
||||
line = linecache.getline(filename,lineno).rstrip()
|
||||
|
||||
data = "%s:%i: %s" % (name, lineno, line)
|
||||
|
||||
print "%s%s" % (data, mem.rjust(80 - len(data)))
|
||||
|
||||
return traceit_memory
|
||||
|
||||
lines = {}
|
||||
|
||||
def traceit(frame, event, arg):
|
||||
if event == "line":
|
||||
try:
|
||||
filename = frame.f_globals["__file__"]
|
||||
|
||||
if filename.endswith(".pyc") or filename.endswith(".pyo"):
|
||||
filename = filename[:-1]
|
||||
|
||||
name = frame.f_globals["__name__"]
|
||||
|
||||
lineno = frame.f_lineno
|
||||
line = linecache.getline(filename,lineno).rstrip()
|
||||
|
||||
data = "%s:%i: %s" % (name, lineno, line)
|
||||
|
||||
print time.time(), data
|
||||
|
||||
#if data in lines:
|
||||
# lines[data] += 1
|
||||
#else:
|
||||
# lines[data] = 1
|
||||
|
||||
except Exception, e:
|
||||
print e
|
||||
|
||||
return traceit
|
||||
|
||||
def main():
|
||||
import urk
|
||||
urk.main()
|
||||
|
||||
sys.settrace(traceit)
|
||||
main()
|
811
services/console/lib/purk/widgets.py
Normal file
811
services/console/lib/purk/widgets.py
Normal file
@ -0,0 +1,811 @@
|
||||
import codecs
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
import gtk.gdk
|
||||
import pango
|
||||
|
||||
from conf import conf
|
||||
import parse_mirc
|
||||
import windows
|
||||
|
||||
import servers
|
||||
|
||||
# Window activity Constants
|
||||
HILIT = 'h'
|
||||
TEXT ='t'
|
||||
EVENT = 'e'
|
||||
|
||||
ACTIVITY_MARKUP = {
|
||||
HILIT: "<span style='italic' foreground='#00F'>%s</span>",
|
||||
TEXT: "<span foreground='#ca0000'>%s</span>",
|
||||
EVENT: "<span foreground='#363'>%s</span>",
|
||||
}
|
||||
|
||||
# This holds all tags for all windows ever
|
||||
tag_table = gtk.TextTagTable()
|
||||
|
||||
link_tag = gtk.TextTag('link')
|
||||
link_tag.set_property('underline', pango.UNDERLINE_SINGLE)
|
||||
|
||||
indent_tag = gtk.TextTag('indent')
|
||||
indent_tag.set_property('indent', -20)
|
||||
|
||||
tag_table.add(link_tag)
|
||||
tag_table.add(indent_tag)
|
||||
|
||||
#FIXME: MEH hates dictionaries, they remind him of the bad words
|
||||
styles = {}
|
||||
|
||||
def style_me(widget, style):
|
||||
widget.set_style(styles.get(style))
|
||||
|
||||
def set_style(widget_name, style):
|
||||
if style:
|
||||
# FIXME: find a better way...
|
||||
dummy = gtk.Label()
|
||||
dummy.set_style(None)
|
||||
|
||||
def apply_style_fg(value):
|
||||
dummy.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse(value))
|
||||
|
||||
def apply_style_bg(value):
|
||||
dummy.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(value))
|
||||
|
||||
def apply_style_font(value):
|
||||
dummy.modify_font(pango.FontDescription(value))
|
||||
|
||||
style_functions = (
|
||||
('fg', apply_style_fg),
|
||||
('bg', apply_style_bg),
|
||||
('font', apply_style_font),
|
||||
)
|
||||
|
||||
for name, f in style_functions:
|
||||
if name in style:
|
||||
f(style[name])
|
||||
|
||||
style = dummy.rc_get_style()
|
||||
else:
|
||||
style = None
|
||||
|
||||
styles[widget_name] = style
|
||||
|
||||
def menu_from_list(alist):
|
||||
while alist and not alist[-1]:
|
||||
alist.pop(-1)
|
||||
|
||||
last = None
|
||||
for item in alist:
|
||||
if item != last:
|
||||
if item:
|
||||
if len(item) == 2:
|
||||
name, function = item
|
||||
|
||||
menuitem = gtk.ImageMenuItem(name)
|
||||
|
||||
elif len(item) == 3:
|
||||
name, stock_id, function = item
|
||||
|
||||
if isinstance(stock_id, bool):
|
||||
menuitem = gtk.CheckMenuItem(name)
|
||||
menuitem.set_active(stock_id)
|
||||
else:
|
||||
menuitem = gtk.ImageMenuItem(stock_id)
|
||||
|
||||
if isinstance(function, list):
|
||||
submenu = gtk.Menu()
|
||||
for subitem in menu_from_list(function):
|
||||
submenu.append(subitem)
|
||||
menuitem.set_submenu(submenu)
|
||||
|
||||
else:
|
||||
menuitem.connect("activate", lambda a, f: f(), function)
|
||||
|
||||
yield menuitem
|
||||
|
||||
else:
|
||||
yield gtk.SeparatorMenuItem()
|
||||
|
||||
last = item
|
||||
|
||||
class Nicklist(gtk.TreeView):
|
||||
def click(self, event):
|
||||
if event.button == 3:
|
||||
x, y = event.get_coords()
|
||||
|
||||
(data,), path, x, y = self.get_path_at_pos(int(x), int(y))
|
||||
|
||||
c_data = self.events.data(window=self.win, data=self[data], menu=[])
|
||||
|
||||
self.events.trigger("ListRightClick", c_data)
|
||||
|
||||
if c_data.menu:
|
||||
menu = gtk.Menu()
|
||||
for item in menu_from_list(c_data.menu):
|
||||
menu.append(item)
|
||||
menu.show_all()
|
||||
menu.popup(None, None, None, event.button, event.time)
|
||||
|
||||
elif event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
|
||||
x, y = event.get_coords()
|
||||
|
||||
(data,), path, x, y = self.get_path_at_pos(int(x), int(y))
|
||||
|
||||
self.events.trigger("ListDoubleClick", window=self.win, target=self[data])
|
||||
|
||||
def __getitem__(self, pos):
|
||||
return self.get_model()[pos][0]
|
||||
|
||||
def __setitem__(self, pos, name_markup):
|
||||
realname, markedupname, sortkey = name_markup
|
||||
|
||||
self.get_model()[pos] = realname, markedupname, sortkey
|
||||
|
||||
def __len__(self):
|
||||
return len(self.get_model())
|
||||
|
||||
def index(self, item):
|
||||
for i, x in enumerate(self):
|
||||
if x == item:
|
||||
return i
|
||||
|
||||
return -1
|
||||
|
||||
def append(self, realname, markedupname, sortkey):
|
||||
self.get_model().append((realname, markedupname, sortkey))
|
||||
|
||||
def insert(self, pos, realname, markedupname, sortkey):
|
||||
self.get_model().insert(pos, (realname, markedupname, sortkey))
|
||||
|
||||
def replace(self, names):
|
||||
self.set_model(gtk.ListStore(str, str, str))
|
||||
|
||||
self.insert_column_with_attributes(
|
||||
0, '', gtk.CellRendererText(), markup=1
|
||||
).set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
|
||||
|
||||
for name in names:
|
||||
self.append(*name)
|
||||
|
||||
self.get_model().set_sort_column_id(2, gtk.SORT_ASCENDING)
|
||||
|
||||
def remove(self, realname):
|
||||
index = self.index(realname)
|
||||
|
||||
if index == -1:
|
||||
raise ValueError
|
||||
|
||||
self.get_model().remove(self.get_model().iter_nth_child(None, index))
|
||||
|
||||
def clear(self):
|
||||
self.get_model().clear()
|
||||
|
||||
def __iter__(self):
|
||||
return (r[0] for r in self.get_model())
|
||||
|
||||
def __init__(self, window, core):
|
||||
self.win = window
|
||||
self.core = core
|
||||
self.events = core.events
|
||||
|
||||
gtk.TreeView.__init__(self)
|
||||
|
||||
self.replace(())
|
||||
|
||||
self.set_headers_visible(False)
|
||||
self.set_property("fixed-height-mode", True)
|
||||
self.connect("button-press-event", Nicklist.click)
|
||||
self.connect_after("button-release-event", lambda *a: True)
|
||||
|
||||
style_me(self, "nicklist")
|
||||
|
||||
# Label used to display/edit your current nick on a network
|
||||
class NickEditor(gtk.EventBox):
|
||||
def nick_change(self, entry):
|
||||
oldnick, newnick = self.label.get_text(), entry.get_text()
|
||||
|
||||
if newnick and newnick != oldnick:
|
||||
self.events.run('nick %s' % newnick, self.win, self.win.network)
|
||||
|
||||
self.win.input.grab_focus()
|
||||
|
||||
def update(self, nick=None):
|
||||
self.label.set_text(nick or self.win.network.me)
|
||||
|
||||
def to_edit_mode(self, widget, event):
|
||||
if self.label not in self.get_children():
|
||||
return
|
||||
|
||||
if getattr(event, 'button', None) == 3:
|
||||
c_data = self.events.data(window=self.win, menu=[])
|
||||
self.events.trigger("NickEditMenu", c_data)
|
||||
|
||||
if c_data.menu:
|
||||
menu = gtk.Menu()
|
||||
for item in menu_from_list(c_data.menu):
|
||||
menu.append(item)
|
||||
menu.show_all()
|
||||
menu.popup(None, None, None, event.button, event.time)
|
||||
|
||||
else:
|
||||
entry = gtk.Entry()
|
||||
entry.set_text(self.label.get_text())
|
||||
entry.connect('activate', self.nick_change)
|
||||
entry.connect('focus-out-event', self.to_show_mode)
|
||||
|
||||
self.remove(self.label)
|
||||
self.add(entry)
|
||||
self.window.set_cursor(None)
|
||||
|
||||
entry.show()
|
||||
entry.grab_focus()
|
||||
|
||||
def to_show_mode(self, widget, event):
|
||||
self.remove(widget)
|
||||
self.add(self.label)
|
||||
self.win.input.grab_focus()
|
||||
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
|
||||
|
||||
def __init__(self, window, core):
|
||||
gtk.EventBox.__init__(self)
|
||||
self.events = core.events
|
||||
self.win = window
|
||||
|
||||
self.label = gtk.Label()
|
||||
self.label.set_padding(5, 0)
|
||||
self.add(self.label)
|
||||
|
||||
self.connect("button-press-event", self.to_edit_mode)
|
||||
|
||||
self.update()
|
||||
|
||||
self.connect(
|
||||
"realize",
|
||||
lambda *a: self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
|
||||
)
|
||||
|
||||
# The entry which you type in to send messages
|
||||
class TextInput(gtk.Entry):
|
||||
# Generates an input event
|
||||
def entered_text(self, ctrl):
|
||||
#for a in globals():
|
||||
# print a
|
||||
#print events.__file__
|
||||
#self.core.run_command(self.text)
|
||||
for line in self.text.splitlines():
|
||||
if line:
|
||||
e_data = self.events.data(
|
||||
window=self.win, network=self.win.network,
|
||||
text=line, ctrl=ctrl
|
||||
)
|
||||
self.events.trigger('Input', e_data)
|
||||
if not e_data.done:
|
||||
self.events.run(line, self.win, self.win.network)
|
||||
|
||||
self.text = ''
|
||||
|
||||
def _set_selection(self, s):
|
||||
if s:
|
||||
self.select_region(*s)
|
||||
else:
|
||||
self.select_region(self.cursor, self.cursor)
|
||||
|
||||
#some nice toys for the scriptors
|
||||
text = property(gtk.Entry.get_text, gtk.Entry.set_text)
|
||||
cursor = property(gtk.Entry.get_position, gtk.Entry.set_position)
|
||||
selection = property(gtk.Entry.get_selection_bounds, _set_selection)
|
||||
|
||||
def insert(self, text):
|
||||
self.do_insert_at_cursor(self, text)
|
||||
|
||||
#hack to stop it selecting the text when we focus
|
||||
def do_grab_focus(self):
|
||||
temp = self.text, (self.selection or (self.cursor,)*2)
|
||||
self.text = ''
|
||||
gtk.Entry.do_grab_focus(self)
|
||||
self.text, self.selection = temp
|
||||
|
||||
def keypress(self, event):
|
||||
keychar = (
|
||||
(gtk.gdk.CONTROL_MASK, '^'),
|
||||
(gtk.gdk.SHIFT_MASK, '+'),
|
||||
(gtk.gdk.MOD1_MASK, '!')
|
||||
)
|
||||
|
||||
key = ''
|
||||
for keymod, char in keychar:
|
||||
# we make this an int, because otherwise it leaks
|
||||
if int(event.state) & keymod:
|
||||
key += char
|
||||
key += gtk.gdk.keyval_name(event.keyval)
|
||||
|
||||
self.events.trigger('KeyPress', key=key, string=event.string, window=self.win)
|
||||
|
||||
if key == "^Return":
|
||||
self.entered_text(True)
|
||||
|
||||
up = gtk.gdk.keyval_from_name("Up")
|
||||
down = gtk.gdk.keyval_from_name("Down")
|
||||
tab = gtk.gdk.keyval_from_name("Tab")
|
||||
|
||||
return event.keyval in (up, down, tab)
|
||||
|
||||
def __init__(self, window, core):
|
||||
gtk.Entry.__init__(self)
|
||||
self.events = core.events
|
||||
self.core = core
|
||||
self.win = window
|
||||
|
||||
# we don't want key events to propogate so we stop them in connect_after
|
||||
self.connect('key-press-event', TextInput.keypress)
|
||||
self.connect_after('key-press-event', lambda *a: True)
|
||||
|
||||
self.connect('activate', TextInput.entered_text, False)
|
||||
|
||||
gobject.type_register(TextInput)
|
||||
|
||||
def prop_to_gtk(textview, (prop, val)):
|
||||
if val == parse_mirc.BOLD:
|
||||
val = pango.WEIGHT_BOLD
|
||||
|
||||
elif val == parse_mirc.UNDERLINE:
|
||||
val = pango.UNDERLINE_SINGLE
|
||||
|
||||
return {prop: val}
|
||||
|
||||
def word_from_pos(text, pos):
|
||||
if text[pos] == ' ':
|
||||
return ' ', pos, pos+1
|
||||
|
||||
else:
|
||||
fr = text[:pos].split(" ")[-1]
|
||||
to = text[pos:].split(" ")[0]
|
||||
|
||||
return fr + to, pos - len(fr), pos + len(to)
|
||||
|
||||
def get_iter_at_coords(view, x, y):
|
||||
return view.get_iter_at_location(
|
||||
*view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, int(x), int(y))
|
||||
)
|
||||
|
||||
def get_event_at_iter(view, iter, core):
|
||||
buffer = view.get_buffer()
|
||||
|
||||
line_strt = buffer.get_iter_at_line(iter.get_line())
|
||||
line_end = line_strt.copy()
|
||||
line_end.forward_lines(1)
|
||||
|
||||
pos = iter.get_line_offset()
|
||||
|
||||
#Caveat: text must be a unicode string, not utf-8 encoded; otherwise our
|
||||
# offsets will be off when we use anything outside 7-bit ascii
|
||||
#gtk.TextIter.get_text returns unicode but gtk.TextBuffer.get_text does not
|
||||
text = line_strt.get_text(line_end).rstrip("\n")
|
||||
|
||||
word, fr, to = word_from_pos(text, pos)
|
||||
|
||||
return core.events.data(
|
||||
window=view.win, pos=pos, text=text,
|
||||
target=word, target_fr=fr, target_to=to,
|
||||
)
|
||||
|
||||
class TextOutput(gtk.TextView):
|
||||
def copy(self):
|
||||
startend = self.get_buffer().get_selection_bounds()
|
||||
|
||||
tagsandtext = []
|
||||
if startend:
|
||||
start, end = startend
|
||||
|
||||
while not start.equal(end):
|
||||
tags_at_iter = {}
|
||||
for tag in start.get_tags():
|
||||
try:
|
||||
tagname, tagval = eval(tag.get_property('name'))
|
||||
tags_at_iter[tagname] = tagval
|
||||
except NameError:
|
||||
continue
|
||||
|
||||
tagsandtext.append((dict(tags_at_iter), start.get_char()))
|
||||
start.forward_char()
|
||||
|
||||
text = parse_mirc.unparse_mirc(tagsandtext)
|
||||
|
||||
gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD).set_text(text)
|
||||
gtk.clipboard_get(gtk.gdk.SELECTION_PRIMARY).set_text(text)
|
||||
|
||||
return text
|
||||
|
||||
def clear(self):
|
||||
self.get_buffer().set_text('')
|
||||
|
||||
def get_y(self):
|
||||
rect = self.get_visible_rect()
|
||||
return rect.y
|
||||
|
||||
def set_y(self,y):
|
||||
iter = self.get_iter_at_location(0, y)
|
||||
if self.get_iter_location(iter).y < y:
|
||||
self.forward_display_line(iter)
|
||||
yalign = float(self.get_iter_location(iter).y-y)/self.height
|
||||
self.scroll_to_iter(iter, 0, True, 0, yalign)
|
||||
|
||||
self.check_autoscroll()
|
||||
|
||||
def get_ymax(self):
|
||||
buffer = self.get_buffer()
|
||||
return sum(self.get_line_yrange(buffer.get_end_iter())) - self.height
|
||||
|
||||
def get_height(self):
|
||||
return self.get_visible_rect().height
|
||||
|
||||
y = property(get_y, set_y)
|
||||
ymax = property(get_ymax)
|
||||
height = property(get_height)
|
||||
|
||||
# the unknowing print weird things to our text widget function
|
||||
def write(self, text, line_ending='\n', fg=None):
|
||||
if not isinstance(text, unicode):
|
||||
try:
|
||||
text = codecs.utf_8_decode(text)[0]
|
||||
except:
|
||||
text = codecs.latin_1_decode(text)[0]
|
||||
tags, text = parse_mirc.parse_mirc(text)
|
||||
|
||||
if fg:
|
||||
tags.append({'data': ("foreground", isinstance(fg, basestring) and ('#%s'%fg) or parse_mirc.get_mirc_color(fg)), 'from': 0, 'to': len(text)})
|
||||
|
||||
buffer = self.get_buffer()
|
||||
|
||||
cc = buffer.get_char_count()
|
||||
|
||||
buffer.insert_with_tags_by_name(
|
||||
buffer.get_end_iter(),
|
||||
text + line_ending,
|
||||
'indent'
|
||||
)
|
||||
|
||||
for tag in tags:
|
||||
tag_name = str(tag['data'])
|
||||
|
||||
if not tag_table.lookup(tag_name):
|
||||
buffer.create_tag(tag_name, **prop_to_gtk(self, tag['data']))
|
||||
|
||||
buffer.apply_tag_by_name(
|
||||
tag_name,
|
||||
buffer.get_iter_at_offset(tag['from'] + cc),
|
||||
buffer.get_iter_at_offset(tag['to'] + cc)
|
||||
)
|
||||
|
||||
def popup(self, menu):
|
||||
hover_iter = get_iter_at_coords(self, *self.hover_coords)
|
||||
|
||||
menuitems = []
|
||||
if not hover_iter.ends_line():
|
||||
c_data = get_event_at_iter(self, hover_iter)
|
||||
c_data.menu = []
|
||||
|
||||
self.events.trigger("RightClick", c_data)
|
||||
|
||||
menuitems = c_data.menu
|
||||
|
||||
if not menuitems:
|
||||
c_data = self.events.data(menu=[])
|
||||
self.events.trigger("MainMenu", c_data)
|
||||
|
||||
menuitems = c_data.menu
|
||||
|
||||
for child in menu.get_children():
|
||||
menu.remove(child)
|
||||
|
||||
for item in menu_from_list(menuitems):
|
||||
menu.append(item)
|
||||
|
||||
menu.show_all()
|
||||
|
||||
def mousedown(self, event):
|
||||
if event.button == 3:
|
||||
self.hover_coords = event.get_coords()
|
||||
|
||||
def mouseup(self, event):
|
||||
if not self.get_buffer().get_selection_bounds():
|
||||
if event.button == 1:
|
||||
hover_iter = get_iter_at_coords(self, event.x, event.y)
|
||||
|
||||
if not hover_iter.ends_line():
|
||||
c_data = get_event_at_iter(self, hover_iter, self.core)
|
||||
|
||||
self.events.trigger("Click", c_data)
|
||||
|
||||
if self.is_focus():
|
||||
self.win.focus()
|
||||
|
||||
def clear_hover(self, _event=None):
|
||||
buffer = self.get_buffer()
|
||||
|
||||
for fr, to in self.linking:
|
||||
buffer.remove_tag_by_name(
|
||||
"link",
|
||||
buffer.get_iter_at_mark(fr),
|
||||
buffer.get_iter_at_mark(to)
|
||||
)
|
||||
|
||||
self.linking = set()
|
||||
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(None)
|
||||
|
||||
def hover(self, event):
|
||||
if self.linking:
|
||||
self.clear_hover()
|
||||
|
||||
hover_iter = get_iter_at_coords(self, event.x, event.y)
|
||||
|
||||
if not hover_iter.ends_line():
|
||||
h_data = get_event_at_iter(self, hover_iter, self.core)
|
||||
h_data.tolink = set()
|
||||
|
||||
self.events.trigger("Hover", h_data)
|
||||
|
||||
if h_data.tolink:
|
||||
buffer = self.get_buffer()
|
||||
|
||||
offset = buffer.get_iter_at_line(
|
||||
hover_iter.get_line()
|
||||
).get_offset()
|
||||
|
||||
for fr, to in h_data.tolink:
|
||||
fr = buffer.get_iter_at_offset(offset + fr)
|
||||
to = buffer.get_iter_at_offset(offset + to)
|
||||
|
||||
buffer.apply_tag_by_name("link", fr, to)
|
||||
|
||||
self.linking.add(
|
||||
(buffer.create_mark(None, fr),
|
||||
buffer.create_mark(None, to))
|
||||
)
|
||||
|
||||
self.get_window(
|
||||
gtk.TEXT_WINDOW_TEXT
|
||||
).set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
|
||||
|
||||
self.get_pointer()
|
||||
|
||||
def scroll(self, _allocation=None):
|
||||
if self.autoscroll:
|
||||
def do_scroll():
|
||||
self.scroller.value = self.scroller.upper - self.scroller.page_size
|
||||
self._scrolling = False
|
||||
|
||||
if not self._scrolling:
|
||||
self._scrolling = gobject.idle_add(do_scroll)
|
||||
|
||||
def check_autoscroll(self, *args):
|
||||
def set_to_scroll():
|
||||
self.autoscroll = self.scroller.value + self.scroller.page_size >= self.scroller.upper
|
||||
|
||||
gobject.idle_add(set_to_scroll)
|
||||
|
||||
def __init__(self, core, window, buffer=None):
|
||||
if not buffer:
|
||||
buffer = gtk.TextBuffer(tag_table)
|
||||
|
||||
gtk.TextView.__init__(self, buffer)
|
||||
self.core = core
|
||||
self.events = core.events
|
||||
self.win = window
|
||||
|
||||
self.set_size_request(0, -1)
|
||||
|
||||
self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
|
||||
self.set_editable(False)
|
||||
self.set_cursor_visible(False)
|
||||
|
||||
self.set_property("left-margin", 3)
|
||||
self.set_property("right-margin", 3)
|
||||
|
||||
self.linking = set()
|
||||
|
||||
self.add_events(gtk.gdk.POINTER_MOTION_HINT_MASK)
|
||||
self.add_events(gtk.gdk.LEAVE_NOTIFY_MASK)
|
||||
|
||||
self.connect('populate-popup', TextOutput.popup)
|
||||
self.connect('motion-notify-event', TextOutput.hover)
|
||||
self.connect('button-press-event', TextOutput.mousedown)
|
||||
self.connect('button-release-event', TextOutput.mouseup)
|
||||
self.connect_after('button-release-event', lambda *a: True)
|
||||
self.connect('leave-notify-event', TextOutput.clear_hover)
|
||||
|
||||
self.hover_coords = 0, 0
|
||||
|
||||
self.autoscroll = True
|
||||
self._scrolling = False
|
||||
self.scroller = gtk.Adjustment()
|
||||
|
||||
def setup_scroll(self, _adj, vadj):
|
||||
self.scroller = vadj
|
||||
|
||||
if vadj:
|
||||
def set_scroll(adj):
|
||||
self.autoscroll = adj.value + adj.page_size >= adj.upper
|
||||
|
||||
vadj.connect("value-changed", set_scroll)
|
||||
|
||||
self.connect("set-scroll-adjustments", setup_scroll)
|
||||
self.connect("size-allocate", TextOutput.scroll)
|
||||
|
||||
def set_cursor(widget):
|
||||
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(None)
|
||||
|
||||
self.connect("realize", set_cursor)
|
||||
|
||||
style_me(self, "view")
|
||||
|
||||
class WindowLabel(gtk.EventBox):
|
||||
def update(self):
|
||||
title = self.win.get_title()
|
||||
|
||||
for escapes in (('&','&'), ('<','<'), ('>','>')):
|
||||
title = title.replace(*escapes)
|
||||
|
||||
for a_type in (HILIT, TEXT, EVENT):
|
||||
if a_type in self.win.activity:
|
||||
title = ACTIVITY_MARKUP[a_type] % title
|
||||
break
|
||||
|
||||
self.label.set_markup(title)
|
||||
|
||||
def tab_popup(self, event):
|
||||
if event.button == 3: # right click
|
||||
c_data = self.events.data(window=self.win, menu=[])
|
||||
self.events.trigger("WindowMenu", c_data)
|
||||
|
||||
c_data.menu += [
|
||||
None,
|
||||
("Close", gtk.STOCK_CLOSE, self.win.close),
|
||||
]
|
||||
|
||||
menu = gtk.Menu()
|
||||
for item in menu_from_list(c_data.menu):
|
||||
menu.append(item)
|
||||
menu.show_all()
|
||||
menu.popup(None, None, None, event.button, event.time)
|
||||
|
||||
def __init__(self, window, core):
|
||||
gtk.EventBox.__init__(self)
|
||||
self.core = core
|
||||
self.events = core.events
|
||||
|
||||
self.win = window
|
||||
self.connect("button-press-event", WindowLabel.tab_popup)
|
||||
|
||||
self.label = gtk.Label()
|
||||
self.add(self.label)
|
||||
|
||||
self.update()
|
||||
self.show_all()
|
||||
|
||||
class FindBox(gtk.HBox):
|
||||
def remove(self, *args):
|
||||
self.parent.remove(self)
|
||||
self.win.focus()
|
||||
|
||||
def clicked(self, button, search_down=False):
|
||||
text = self.textbox.get_text()
|
||||
|
||||
if not text:
|
||||
return
|
||||
|
||||
buffer = self.win.output.get_buffer()
|
||||
|
||||
if buffer.get_selection_bounds():
|
||||
if button == self.down:
|
||||
_, cursor_iter = buffer.get_selection_bounds()
|
||||
else:
|
||||
cursor_iter, _ = buffer.get_selection_bounds()
|
||||
else:
|
||||
cursor_iter = buffer.get_end_iter()
|
||||
|
||||
if search_down:
|
||||
cursor = cursor_iter.forward_search(
|
||||
text, gtk.TEXT_SEARCH_VISIBLE_ONLY
|
||||
)
|
||||
else:
|
||||
cursor = cursor_iter.backward_search(
|
||||
text, gtk.TEXT_SEARCH_VISIBLE_ONLY
|
||||
)
|
||||
|
||||
if not cursor:
|
||||
return
|
||||
|
||||
fr, to = cursor
|
||||
|
||||
if button == self.up:
|
||||
buffer.place_cursor(fr)
|
||||
self.win.output.scroll_to_iter(fr, 0)
|
||||
elif button == self.down:
|
||||
buffer.place_cursor(to)
|
||||
self.win.output.scroll_to_iter(to, 0)
|
||||
|
||||
buffer.select_range(*cursor)
|
||||
|
||||
cursor_iter = buffer.get_iter_at_mark(buffer.get_insert())
|
||||
|
||||
def __init__(self, window):
|
||||
gtk.HBox.__init__(self)
|
||||
|
||||
self.win = window
|
||||
|
||||
self.up = gtk.Button(stock='gtk-go-up')
|
||||
self.down = gtk.Button(stock='gtk-go-down')
|
||||
|
||||
self.up.connect('clicked', self.clicked)
|
||||
self.down.connect('clicked', self.clicked, True)
|
||||
|
||||
self.up.set_property('can_focus', False)
|
||||
self.down.set_property('can_focus', False)
|
||||
|
||||
self.textbox = gtk.Entry()
|
||||
|
||||
self.textbox.connect('focus-out-event', self.remove)
|
||||
self.textbox.connect('activate', self.clicked)
|
||||
|
||||
self.pack_start(gtk.Label('Find:'), expand=False)
|
||||
self.pack_start(self.textbox)
|
||||
|
||||
self.pack_start(self.up, expand=False)
|
||||
self.pack_start(self.down, expand=False)
|
||||
|
||||
self.show_all()
|
||||
|
||||
#class UrkUITabs(gtk.Window):
|
||||
class UrkUITabs(object):
|
||||
def __init__(self, core):
|
||||
# threading stuff
|
||||
gtk.gdk.threads_init()
|
||||
self.core = core
|
||||
self.events = core.events
|
||||
self.tabs = gtk.Notebook()
|
||||
self.tabs.set_property(
|
||||
"tab-pos",
|
||||
conf.get("ui-gtk/tab-pos", gtk.POS_BOTTOM)
|
||||
)
|
||||
|
||||
self.tabs.set_scrollable(True)
|
||||
self.tabs.set_property("can-focus", False)
|
||||
|
||||
self.box = gtk.VBox(False)
|
||||
self.box.pack_end(self.tabs)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.tabs.get_children())
|
||||
|
||||
def __len__(self):
|
||||
return self.tabs.get_n_pages()
|
||||
|
||||
def exit(self, *args):
|
||||
self.events.trigger("Exit")
|
||||
gtk.main_level() and gtk.main_quit()
|
||||
|
||||
def get_active(self):
|
||||
return self.tabs.get_nth_page(self.tabs.get_current_page())
|
||||
|
||||
def set_active(self, window):
|
||||
self.tabs.set_current_page(self.tabs.page_num(window))
|
||||
|
||||
def add(self, window):
|
||||
for pos in reversed(range(self.tabs.get_n_pages())):
|
||||
if self.tabs.get_nth_page(pos).network == window.network:
|
||||
break
|
||||
else:
|
||||
pos = self.tabs.get_n_pages() - 1
|
||||
|
||||
self.tabs.insert_page(window, WindowLabel(window, self.core), pos+1)
|
||||
|
||||
def remove(self, window):
|
||||
self.tabs.remove_page(self.tabs.page_num(window))
|
||||
|
||||
def update(self, window):
|
||||
self.tabs.get_tab_label(window).update()
|
||||
|
||||
def show_all(self):
|
||||
self.box.show_all()
|
298
services/console/lib/purk/windows.py
Normal file
298
services/console/lib/purk/windows.py
Normal file
@ -0,0 +1,298 @@
|
||||
import gtk
|
||||
|
||||
import irc
|
||||
from conf import conf
|
||||
import widgets
|
||||
|
||||
#manager = widgets.UrkUITabs()
|
||||
|
||||
def append(window, manager):
|
||||
manager.add(window)
|
||||
|
||||
def remove(window, manager):
|
||||
manager.remove(window)
|
||||
|
||||
# i don't want to have to call this
|
||||
window.destroy()
|
||||
|
||||
def new(wclass, network, id, core):
|
||||
if network is None:
|
||||
network = irc.dummy_network
|
||||
|
||||
w = get(wclass, network, id, core)
|
||||
if not w:
|
||||
w = wclass(network, id, core)
|
||||
append(w, core.manager)
|
||||
|
||||
return w
|
||||
|
||||
def get(windowclass, network, id, core):
|
||||
if network:
|
||||
id = network.norm_case(id)
|
||||
|
||||
for w in core.manager:
|
||||
if (type(w), w.network, w.id) == (windowclass, network, id):
|
||||
return w
|
||||
|
||||
def get_with(manager, wclass=None, network=None, id=None):
|
||||
if network and id:
|
||||
id = network.norm_case(id)
|
||||
|
||||
for w in list(manager):
|
||||
for to_find, found in ((wclass, type(w)), (network, w.network), (id, w.id)):
|
||||
# to_find might be False but not None (if it's a DummyNetwork)
|
||||
if to_find is not None and to_find != found:
|
||||
break
|
||||
else:
|
||||
yield w
|
||||
|
||||
def get_default(network, manager):
|
||||
|
||||
window = manager.get_active()
|
||||
if window.network == network:
|
||||
return window
|
||||
|
||||
# There can be only one...
|
||||
for window in get_with(network=network):
|
||||
return window
|
||||
|
||||
class Window(gtk.VBox):
|
||||
need_vbox_init = True
|
||||
|
||||
def mutate(self, newclass, network, id):
|
||||
isactive = self == manager.get_active()
|
||||
self.hide()
|
||||
|
||||
for child in self.get_children():
|
||||
self.remove(child)
|
||||
|
||||
self.__class__ = newclass
|
||||
self.__init__(network, id)
|
||||
self.update()
|
||||
if isactive:
|
||||
self.activate()
|
||||
|
||||
def transfer_text(self, _widget, event):
|
||||
if event.string and not self.input.is_focus():
|
||||
self.input.grab_focus()
|
||||
self.input.set_position(-1)
|
||||
self.input.event(event)
|
||||
|
||||
def write(self, text, activity_type=widgets.EVENT, line_ending='\n', fg=None):
|
||||
if self.manager.get_active() != self:
|
||||
self.activity = activity_type
|
||||
self.output.write(text, line_ending, fg)
|
||||
|
||||
def get_id(self):
|
||||
if self.network:
|
||||
return self.network.norm_case(self.rawid)
|
||||
else:
|
||||
return self.rawid
|
||||
|
||||
def set_id(self, id):
|
||||
self.rawid = id
|
||||
self.update()
|
||||
|
||||
id = property(get_id, set_id)
|
||||
|
||||
def get_toplevel_title(self):
|
||||
return self.rawid
|
||||
|
||||
def get_title(self):
|
||||
return self.rawid
|
||||
|
||||
def get_activity(self):
|
||||
return self.__activity
|
||||
|
||||
def set_activity(self, value):
|
||||
if value:
|
||||
self.__activity.add(value)
|
||||
else:
|
||||
self.__activity = set()
|
||||
self.update()
|
||||
|
||||
activity = property(get_activity, set_activity)
|
||||
|
||||
def focus(self):
|
||||
pass
|
||||
|
||||
def activate(self):
|
||||
self.manager.set_active(self)
|
||||
self.focus()
|
||||
|
||||
def close(self):
|
||||
self.events.trigger("Close", window=self)
|
||||
remove(self, self.manager)
|
||||
|
||||
def update(self):
|
||||
self.manager.update(self)
|
||||
|
||||
def __init__(self, network, id, core):
|
||||
self.manager = core.manager
|
||||
self.events = core.events
|
||||
|
||||
if self.need_vbox_init:
|
||||
#make sure we don't call this an extra time when mutating
|
||||
gtk.VBox.__init__(self, False)
|
||||
self.need_vbox_init = False
|
||||
|
||||
if hasattr(self, "buffer"):
|
||||
self.output = widgets.TextOutput(core, self, self.buffer)
|
||||
else:
|
||||
self.output = widgets.TextOutput(core, self)
|
||||
self.buffer = self.output.get_buffer()
|
||||
|
||||
if hasattr(self, "input"):
|
||||
if self.input.parent:
|
||||
self.input.parent.remove(self.input)
|
||||
else:
|
||||
self.input = widgets.TextInput(self, core)
|
||||
|
||||
self.network = network
|
||||
self.rawid = id
|
||||
|
||||
self.__activity = set()
|
||||
|
||||
class SimpleWindow(Window):
|
||||
def __init__(self, network, id, core):
|
||||
Window.__init__(self, network, id)
|
||||
|
||||
self.focus = self.input.grab_focus
|
||||
self.connect("key-press-event", self.transfer_text)
|
||||
|
||||
self.pack_end(self.input, expand=False)
|
||||
|
||||
topbox = gtk.ScrolledWindow()
|
||||
topbox.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
topbox.add(self.output)
|
||||
|
||||
self.pack_end(topbox)
|
||||
|
||||
self.show_all()
|
||||
|
||||
class StatusWindow(Window):
|
||||
def get_toplevel_title(self):
|
||||
return '%s - %s' % (self.network.me, self.get_title())
|
||||
|
||||
def get_title(self):
|
||||
# Something about self.network.isupport
|
||||
if self.network.status:
|
||||
return "%s" % self.network.server
|
||||
else:
|
||||
return "[%s]" % self.network.server
|
||||
|
||||
def __init__(self, network, id, core):
|
||||
Window.__init__(self, network, id, core)
|
||||
|
||||
self.nick_label = widgets.NickEditor(self, core)
|
||||
|
||||
self.focus = self.input.grab_focus
|
||||
self.connect("key-press-event", self.transfer_text)
|
||||
self.manager = core.manager
|
||||
botbox = gtk.HBox()
|
||||
botbox.pack_start(self.input)
|
||||
botbox.pack_end(self.nick_label, expand=False)
|
||||
|
||||
self.pack_end(botbox, expand=False)
|
||||
|
||||
topbox = gtk.ScrolledWindow()
|
||||
topbox.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
topbox.add(self.output)
|
||||
|
||||
self.pack_end(topbox)
|
||||
|
||||
self.show_all()
|
||||
|
||||
class QueryWindow(Window):
|
||||
def __init__(self, network, id, core):
|
||||
Window.__init__(self, network, id, core)
|
||||
|
||||
self.nick_label = widgets.NickEditor(self, core)
|
||||
|
||||
self.focus = self.input.grab_focus
|
||||
self.connect("key-press-event", self.transfer_text)
|
||||
|
||||
botbox = gtk.HBox()
|
||||
botbox.pack_start(self.input)
|
||||
botbox.pack_end(self.nick_label, expand=False)
|
||||
|
||||
self.pack_end(botbox, expand=False)
|
||||
|
||||
topbox = gtk.ScrolledWindow()
|
||||
topbox.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
topbox.add(self.output)
|
||||
|
||||
self.pack_end(topbox)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def move_nicklist(paned, event):
|
||||
paned._moving = (
|
||||
event.type == gtk.gdk._2BUTTON_PRESS,
|
||||
paned.get_position()
|
||||
)
|
||||
|
||||
def drop_nicklist(paned, event):
|
||||
width = paned.allocation.width
|
||||
pos = paned.get_position()
|
||||
|
||||
double_click, nicklist_pos = paned._moving
|
||||
|
||||
if double_click:
|
||||
# if we're "hidden", then we want to unhide
|
||||
if width - pos <= 10:
|
||||
# get the normal nicklist width
|
||||
conf_nicklist = conf.get("ui-gtk/nicklist-width", 200)
|
||||
|
||||
# if the normal nicklist width is "hidden", then ignore it
|
||||
if conf_nicklist <= 10:
|
||||
paned.set_position(width - 200)
|
||||
else:
|
||||
paned.set_position(width - conf_nicklist)
|
||||
|
||||
# else we hide
|
||||
else:
|
||||
paned.set_position(width)
|
||||
|
||||
else:
|
||||
if pos != nicklist_pos:
|
||||
conf["ui-gtk/nicklist-width"] = width - pos - 6
|
||||
|
||||
class ChannelWindow(Window):
|
||||
def __init__(self, network, id, core):
|
||||
Window.__init__(self, network, id, core)
|
||||
|
||||
self.nicklist = widgets.Nicklist(self, core)
|
||||
self.nick_label = widgets.NickEditor(self, core)
|
||||
|
||||
self.focus = self.input.grab_focus
|
||||
self.connect("key-press-event", self.transfer_text)
|
||||
|
||||
topbox = gtk.ScrolledWindow()
|
||||
topbox.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
topbox.add(self.output)
|
||||
|
||||
nlbox = gtk.ScrolledWindow()
|
||||
nlbox.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||
nlbox.add(self.nicklist)
|
||||
|
||||
nlbox.set_size_request(conf.get("ui-gtk/nicklist-width", 112), -1)
|
||||
|
||||
botbox = gtk.HBox()
|
||||
botbox.pack_start(self.input)
|
||||
botbox.pack_end(self.nick_label, expand=False)
|
||||
|
||||
self.pack_end(botbox, expand=False)
|
||||
|
||||
pane = gtk.HPaned()
|
||||
pane.pack1(topbox, resize=True, shrink=False)
|
||||
pane.pack2(nlbox, resize=False, shrink=True)
|
||||
|
||||
self.nicklist.pos = None
|
||||
|
||||
pane.connect("button-press-event", move_nicklist)
|
||||
pane.connect("button-release-event", drop_nicklist)
|
||||
|
||||
self.pack_end(pane)
|
||||
|
||||
self.show_all()
|
@ -30,15 +30,28 @@ class ObjectTypeRegistry(dbus.service.Object):
|
||||
|
||||
self._types = {}
|
||||
|
||||
self._add_primitive('Text', _('Text'), 'theme:text',
|
||||
[ 'text/plain', 'text/rtf', 'application/pdf',
|
||||
self._add_primitive('Text', _('Text'), 'theme:text-x-generic',
|
||||
['text/plain', 'text/rtf', 'application/pdf',
|
||||
'application/x-pdf', 'text/html',
|
||||
'application/vnd.oasis.opendocument.text',
|
||||
'application/rtf', 'text/rtf' ])
|
||||
self._add_primitive('Image', _('Image'), 'theme:image',
|
||||
[ 'image/png', 'image/gif', 'image/jpeg' ])
|
||||
self._add_primitive('Audio', _('Audio'), 'theme:audio', [ 'audio/ogg' ])
|
||||
self._add_primitive('Video', _('Video'), 'theme:video', [ 'video/ogg' ])
|
||||
'application/rtf', 'text/rtf'])
|
||||
|
||||
self._add_primitive('Image', _('Image'), 'theme:image-x-generic',
|
||||
['image/png', 'image/gif', 'image/jpeg'])
|
||||
|
||||
self._add_primitive('Audio', _('Audio'), 'theme:audio-x-generic',
|
||||
['audio/ogg'])
|
||||
|
||||
self._add_primitive('Video', _('Video'), 'theme:video-x-generic',
|
||||
['video/ogg', 'application/ogg'])
|
||||
|
||||
self._add_primitive('Etoys project', _('Etoys project'),
|
||||
'theme:application-x-squeak-project',
|
||||
['application/x-squeak-project'])
|
||||
|
||||
self._add_primitive('Link', _('Link'),
|
||||
'theme:text-uri-list',
|
||||
['text/x-moz-url', 'text/uri-list'])
|
||||
|
||||
def _add_primitive(self, type_id, name, icon, mime_types):
|
||||
object_type = {'type_id': type_id,
|
||||
@ -51,6 +64,7 @@ class ObjectTypeRegistry(dbus.service.Object):
|
||||
for object_type in self._types.values():
|
||||
if mime_type in object_type['mime_types']:
|
||||
return object_type
|
||||
return None
|
||||
|
||||
@dbus.service.method(_REGISTRY_IFACE,
|
||||
in_signature="s", out_signature="a{sv}")
|
||||
|
@ -26,7 +26,7 @@ class ColorPicker(hippo.CanvasBox, hippo.CanvasItem):
|
||||
self.props.orientation = hippo.ORIENTATION_HORIZONTAL
|
||||
|
||||
self._xo = CanvasIcon(size=style.XLARGE_ICON_SIZE,
|
||||
icon_name='theme:xo')
|
||||
icon_name='theme:computer-xo')
|
||||
self._set_random_colors()
|
||||
self._xo.connect('activated', self._xo_activated_cb)
|
||||
self.append(self._xo)
|
||||
|
@ -20,7 +20,7 @@ from view.BuddyMenu import BuddyMenu
|
||||
|
||||
class BuddyIcon(CanvasIcon):
|
||||
def __init__(self, shell, buddy):
|
||||
CanvasIcon.__init__(self, icon_name='theme:xo',
|
||||
CanvasIcon.__init__(self, icon_name='theme:computer-xo',
|
||||
xo_color=buddy.get_color())
|
||||
|
||||
self._shell = shell
|
||||
|
@ -54,7 +54,7 @@ class ClipboardIcon(CanvasIcon):
|
||||
|
||||
self.palette = ClipboardMenu(self._object_id, self._name, self._percent,
|
||||
self._preview, self._activity,
|
||||
formats and formats[0] == 'application/vnd.olpc-x-sugar')
|
||||
formats and formats[0] == 'application/vnd.olpc-sugar')
|
||||
|
||||
def do_set_property(self, pspec, value):
|
||||
if pspec.name == 'selected':
|
||||
@ -80,7 +80,7 @@ class ClipboardIcon(CanvasIcon):
|
||||
def set_state(self, name, percent, icon_name, preview, activity):
|
||||
cb_service = clipboardservice.get_instance()
|
||||
obj = cb_service.get_object(self._object_id)
|
||||
if obj['FORMATS'] and obj['FORMATS'][0] == 'application/vnd.olpc-x-sugar':
|
||||
if obj['FORMATS'] and obj['FORMATS'][0] == 'application/vnd.olpc-sugar':
|
||||
installable = True
|
||||
else:
|
||||
installable = False
|
||||
@ -88,7 +88,7 @@ class ClipboardIcon(CanvasIcon):
|
||||
if icon_name:
|
||||
self.props.icon_name = icon_name
|
||||
else:
|
||||
self.props.icon_name = 'theme:unknown-object'
|
||||
self.props.icon_name = 'theme:application-octet-stream'
|
||||
|
||||
self._name = name
|
||||
self._percent = percent
|
||||
|
@ -20,5 +20,5 @@ from sugar import profile
|
||||
class MyIcon(CanvasIcon):
|
||||
def __init__(self, size):
|
||||
CanvasIcon.__init__(self, size=size,
|
||||
icon_name='theme:xo',
|
||||
icon_name='theme:computer-xo',
|
||||
xo_color=profile.get_color())
|
||||
|
@ -24,6 +24,7 @@ import gobject
|
||||
import gtk
|
||||
|
||||
from sugar.graphics.canvasicon import CanvasIcon
|
||||
from sugar.graphics.menuitem import MenuItem
|
||||
from sugar.graphics.palette import Palette
|
||||
from sugar.graphics import style
|
||||
from sugar.graphics import xocolor
|
||||
@ -87,14 +88,14 @@ class ActivityIcon(CanvasIcon):
|
||||
|
||||
palette.set_primary_text(self._activity.get_title())
|
||||
|
||||
resume_menu_item = gtk.MenuItem(_('Resume'))
|
||||
resume_menu_item = MenuItem(_('Resume'), 'zoom-activity')
|
||||
resume_menu_item.connect('activate', self._resume_activate_cb)
|
||||
palette.menu.append(resume_menu_item)
|
||||
resume_menu_item.show()
|
||||
|
||||
# FIXME: kludge
|
||||
if self._activity.get_type() != "org.laptop.JournalActivity":
|
||||
stop_menu_item = gtk.MenuItem(_('Stop'))
|
||||
stop_menu_item = MenuItem(_('Stop'), 'activity-stop')
|
||||
stop_menu_item.connect('activate', self._stop_activate_cb)
|
||||
palette.menu.append(stop_menu_item)
|
||||
stop_menu_item.show()
|
||||
|
@ -25,9 +25,11 @@ import logging
|
||||
import os
|
||||
import time
|
||||
import tempfile
|
||||
from hashlib import sha1
|
||||
|
||||
import gtk, gobject
|
||||
import dbus
|
||||
import json
|
||||
|
||||
from sugar import util
|
||||
from sugar.presence import presenceservice
|
||||
@ -276,9 +278,15 @@ class Activity(Window, gtk.Container):
|
||||
self._jobject.metadata['activity'] = self.get_service_name()
|
||||
self._jobject.metadata['activity_id'] = self.get_id()
|
||||
self._jobject.metadata['keep'] = '0'
|
||||
#self._jobject.metadata['buddies'] = ''
|
||||
self._jobject.metadata['preview'] = ''
|
||||
self._jobject.metadata['icon-color'] = profile.get_color().to_string()
|
||||
|
||||
if self._shared_activity is not None:
|
||||
icon_color = self._shared_activity.props.color
|
||||
else:
|
||||
icon_color = profile.get_color().to_string()
|
||||
|
||||
self._jobject.metadata['icon-color'] = icon_color
|
||||
|
||||
self._jobject.file_path = ''
|
||||
datastore.write(self._jobject,
|
||||
reply_handler=self._internal_jobject_create_cb,
|
||||
@ -394,7 +402,15 @@ class Activity(Window, gtk.Container):
|
||||
return base64.b64encode(preview_data)
|
||||
|
||||
def _get_buddies(self):
|
||||
return ''
|
||||
if self._shared_activity is not None:
|
||||
buddies = {}
|
||||
for buddy in self._shared_activity.get_joined_buddies():
|
||||
if not buddy.props.owner:
|
||||
buddy_id = sha1(buddy.props.key).hexdigest()
|
||||
buddies[buddy_id] = [buddy.props.nick, buddy.props.color]
|
||||
return buddies
|
||||
else:
|
||||
return {}
|
||||
|
||||
def save(self):
|
||||
"""Request that the activity is saved to the Journal."""
|
||||
@ -402,11 +418,16 @@ class Activity(Window, gtk.Container):
|
||||
if self._updating_jobject:
|
||||
return
|
||||
|
||||
#self.metadata['buddies'] = self._get_buddies()
|
||||
buddies_dict = self._get_buddies()
|
||||
if buddies_dict:
|
||||
self.metadata['buddies_id'] = json.write(buddies_dict.keys())
|
||||
self.metadata['buddies'] = json.write(self._get_buddies())
|
||||
|
||||
if self._preview is None:
|
||||
self.metadata['preview'] = ''
|
||||
else:
|
||||
self.metadata['preview'] = self._preview
|
||||
|
||||
try:
|
||||
if self._jobject.file_path:
|
||||
self.write_file(self._jobject.file_path)
|
||||
|
@ -116,7 +116,8 @@ class DSObject(object):
|
||||
return activities
|
||||
|
||||
def is_bundle(self):
|
||||
return self.metadata['mime_type'] == 'application/vnd.olpc-x-sugar'
|
||||
return self.metadata['mime_type'] in ['application/vnd.olpc-x-sugar',
|
||||
'application/vnd.olpc-sugar']
|
||||
|
||||
def resume(self, service_name=None):
|
||||
if self.is_bundle():
|
||||
|
@ -133,7 +133,7 @@ class CollapsedEntry(CanvasRoundBox):
|
||||
if self._icon_name:
|
||||
return self._icon_name
|
||||
|
||||
if self._is_bundle():
|
||||
if self.jobject.is_bundle():
|
||||
bundle = Bundle(self.jobject.file_path)
|
||||
self._icon_name = bundle.get_icon()
|
||||
|
||||
@ -159,9 +159,6 @@ class CollapsedEntry(CanvasRoundBox):
|
||||
ti = time.strptime(self.jobject.metadata['mtime'], "%Y-%m-%dT%H:%M:%S")
|
||||
return str(Date(time.mktime(ti)))
|
||||
|
||||
def _is_bundle(self):
|
||||
return self.jobject.metadata['mime_type'] == 'application/vnd.olpc-x-sugar'
|
||||
|
||||
def _format_title(self):
|
||||
return '"%s"' % self.jobject.metadata['title']
|
||||
|
||||
|
@ -45,7 +45,7 @@ class RadioToolButton(gtk.RadioToolButton):
|
||||
self._palette.props.invoker = WidgetInvoker(self.child)
|
||||
|
||||
def set_tooltip(self, text):
|
||||
self._set_palette(Palette(text))
|
||||
self.set_palette(Palette(text))
|
||||
|
||||
def do_expose_event(self, event):
|
||||
if self._palette and self._palette.is_up():
|
||||
|
@ -41,7 +41,7 @@ class ToggleToolButton(gtk.ToggleToolButton):
|
||||
self._palette.props.invoker = WidgetInvoker(self.child)
|
||||
|
||||
def set_tooltip(self, text):
|
||||
self._set_palette(Palette(text))
|
||||
self.set_palette(Palette(text))
|
||||
|
||||
def do_expose_event(self, event):
|
||||
if self._palette and self._palette.is_up():
|
||||
|
Loading…
Reference in New Issue
Block a user