DevConsole: New IRC Client interface

master
Eduardo Silva 17 years ago
parent 7215de2dfc
commit 2ed32d1a11

@ -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')
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 = \

@ -0,0 +1,5 @@
sugardir = $(pkgdatadir)/services/console/interface/irc_client
sugar_PYTHON = \
__init__.py \
irc_client.py

@ -0,0 +1,2 @@
from irc_client import Interface

@ -0,0 +1,10 @@
import purk
class Interface(object):
def __init__(self):
client = purk.Client()
client.show()
client.join_server('irc.freenode.net')
self.widget = client.get_widget()

@ -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 =

@ -0,0 +1,5 @@
sugardir = $(pkgdatadir)/services/console/lib/gui
sugar_PYTHON = \
__init__.py \
treeview.py

@ -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)

@ -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>

@ -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.

@ -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

@ -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.

@ -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;
}
}
}

@ -0,0 +1,94 @@
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)
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()

@ -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)

@ -0,0 +1,298 @@
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()
#print "searching: " + event_name
#for s in all_events:
# print "match: " + s
# if s == event_name:
# print "we got it!"
if event_name in all_events:
result = trigger(event_name, c_data)
if result:
print "* /%s: %s" % (c_data.name, result[0])
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

@ -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)

@ -0,0 +1,328 @@
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.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).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)
#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)

@ -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)

@ -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

@ -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""")

@ -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 (('&','&amp;'), ('<','&lt;'), ('>','&gt;')):
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__)

@ -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)

@ -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

@ -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")

@ -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

@ -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""")

@ -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))

@ -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"

@ -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)

@ -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

@ -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)

@ -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)

@ -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)

@ -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()

@ -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 (('&','&amp;'), ('<','&lt;'), ('>','&gt;')):
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()

@ -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()
Loading…
Cancel
Save