Remove every .pyc, and add AppImageAssistant 0.9.3

master
Ismael Barros 2013-12-25 11:25:07 +01:00
parent 48729aadaf
commit bf1bd8d7f5
52 changed files with 4788 additions and 0 deletions

View File

@ -0,0 +1,288 @@
#!/usr/bin/env python
# /**************************************************************************
#
# Copyright (c) 2005-13 Simon Peter
#
# All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ******************************************************
__version__="0.9.3"
#
# TODO:
# Find out why it freezes on Fedora 12
#
import os, sys
from locale import gettext as _
import shutil
import os
from subprocess import *
import tempfile
import sys
import bz2
import xdgappdir
import commands
import threading
import glob
if len(sys.argv) == 3:
os.system(os.path.dirname(__file__) + "/package %s %s" % (sys.argv[1], sys.argv[2]))
exit(0)
import gtk, vte
import xdgappdir # bundled with this app
import dialogs # part of kiwi; bundled with this app
application_icon = os.path.join(os.path.dirname(sys.argv[0]), "AppImageAssistant.png")
def error(string, fatal=True):
print(string)
if fatal == True:
buttontype = gtk.BUTTONS_CANCEL
else:
buttontype = gtk.BUTTONS_OK
message = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, buttontype, string)
resp = message.run()
message.destroy()
if fatal == True:
exit(1)
'''
returns 1 if yes was pressed,
0 if no was pressed,
-1 if dialog was cancled
'''
d=gtk.GtkWindow()
hbox = gtk.GtkHButtonBox()
def delete_event(widget, event, d):
d.callback_return=-1
return gtk.FALSE
d.connect("delete_event", delete_event, d)
d.add(hbox)
def callback(widget, data):
d=data[0]
data=data[1]
d.hide()
d.callback_return=data
yes = gtk.GtkButton(yes_text)
yes.connect("clicked", callback, (d, 1))
hbox.pack_start(yes)
no = gtk.GtkButton(no_text)
no.connect("clicked", callback, (d, 0))
hbox.pack_start(no)
d.set_modal(gtk.TRUE)
d.show_all()
d.callback_return=None
while d.callback_return==None:
gtk.mainiteration(gtk.TRUE) # block until event occurs
return d.callback_return
def threaded(f):
def wrapper(*args):
t = threading.Thread(target=f, args=args)
t.setDaemon(True)
t.start()
wrapper.__name__ = f.__name__
wrapper.__dict__ = f.__dict__
wrapper.__doc__ = f.__doc__
return wrapper
class Assistant(gtk.Assistant):
def __init__(self):
gtk.Assistant.__init__(self)
self.connect('close', gtk.main_quit)
self.connect('cancel',gtk.main_quit)
#self.connect('prepare', self.callback_prepare)
self.set_icon_from_file(application_icon)
self.set_size_request(640, 480)
self.init_intro_page()
def text_page(self, header, text):
label = gtk.Label(text)
label.show()
label.set_line_wrap(True)
self.append_page(label)
self.set_page_title(label, header)
self.set_page_complete(label, True)
self.set_page_header_image(label, gtk.gdk.pixbuf_new_from_file(application_icon))
def chooser_page(self, header):
chooser = gtk.FileChooserWidget(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
chooser.connect("selection-changed", self.check_if_appdir_callback)
chooser.connect("key-release-event", self.check_if_appdir_callback)
if len(sys.argv) > 1:
chooser.set_current_folder(sys.argv[1])
else:
chooser.set_current_folder(os.environ.get('HOME'))
chooser.show()
self.append_page(chooser)
self.set_page_title(chooser, header)
self.set_page_complete(chooser, False)
self.set_page_header_image(chooser, gtk.gdk.pixbuf_new_from_file(application_icon))
def runner_page(self, callable):
vbox = gtk.VBox()
vbox.set_name("MAIN_RUNNER_PAGE")
label = gtk.Label(_("Running..."))
label.set_line_wrap(True)
vbox.pack_start(label, False, False, 0)
runbox = RunBox(self)
runbox.connect('realize', callable)
vbox.pack_start(runbox, True, True, 0)
self.append_page(vbox)
self.set_page_title(vbox, "Running...")
self.set_page_header_image(vbox, gtk.gdk.pixbuf_new_from_file(application_icon))
vbox.show_all()
self.set_page_complete(vbox, True) ##############################
self.set_page_type(vbox, gtk.ASSISTANT_PAGE_PROGRESS)
# UNTIL HERE IT IS GENERIC ====================================================
def result_page(self):
vbox = gtk.VBox()
icon = gtk.Image()
vbox.pack_start(icon, False, False, 0)
vbox.show_all()
scrolled = gtk.ScrolledWindow()
scrolled.add_with_viewport(vbox)
scrolled.show()
self.append_page(scrolled)
self.set_page_header_image(vbox, gtk.gdk.pixbuf_new_from_file(application_icon))
icon.show()
filetype = Popen(["file", "-k", "-r", self.targetname], stdout=PIPE).communicate()[0]
# print filetype
if "ISO 9660" in filetype and "32-bit LSB executable" in filetype:
icon.set_from_file(os.path.join(os.path.dirname(sys.argv[0]), "Gnome-emblem-default.png"))
self.set_page_title(vbox, "Done")
self.set_page_type(vbox, gtk.ASSISTANT_PAGE_SUMMARY)
basesystems = glob.glob('/System/iso/*.iso')
basesystems.append("/cdrom/casper/filesystem.squashfs")
for basesystem in basesystems:
print basesystem
button = gtk.Button(_("Run in %s") % (basesystem))
button.connect('clicked', self.testrun, basesystem, self.targetname)
vbox.pack_start(button)
button.show()
else:
icon.set_from_file(os.path.join(os.path.dirname(sys.argv[0]), "Gnome-dialog-warning.png"))
self.set_page_title(icon, "An error has occured")
def testrun(self, sender, basesystem, appimage):
# Need an involved workaround because running as root means that we cannot access files inside the AppImage due to permissions
shutil.copyfile(os.path.join(os.path.dirname(sys.argv[0]), "testappimage"), "/tmp/testappimage")
shutil.copyfile(os.path.join(os.path.dirname(sys.argv[0]), "unionfs-fuse"), "/tmp/unionfs-fuse")
os.system("sudo chmod 755 /tmp/testappimage /tmp/unionfs-fuse")
os.system("sudo xterm -hold -e /tmp/testappimage '" + basesystem + "' '" + appimage + "'")
def check_if_appdir_callback(self, widget, dummy=False):
print _("Checking whether %s is an AppDir" % (widget.get_filename()))
self.set_page_complete(widget, True)
candidate = widget.get_filename()
if not os.path.isfile(os.path.join(candidate, "AppRun")):
self.set_page_complete(widget, False)
return
self.H = xdgappdir.AppDirXdgHandler(candidate)
print self.H
if self.H.desktopfile == None:
self.set_page_complete(widget, False)
error(_("Can't find this AppDir's desktop file"), False)
return
if self.H.executable == None:
self.set_page_complete(widget, False)
error(_("Can't find the executable in this AppDir's desktop file"), False)
return
if self.H.icon == None:
self.set_page_complete(widget, False)
error(_("Can't find the icon in this AppDir's desktop file"), False)
return
self.appdir = candidate
try:
self.targetname = os.path.join(os.path.dirname(self.appdir), self.H.name)
except:
self.targetname = os.path.join(os.path.dirname(self.appdir), os.path.basename(self.H.executable))
if os.path.exists(self.targetname):
self.set_page_complete(widget, False)
resp = dialogs.yesno(_("%s already exists, do you want to delete it?") % (self.targetname))
if resp == gtk.RESPONSE_YES:
try:
os.unlink(self.targetname)
self.set_page_complete(widget, True)
self.set_current_page(self.get_current_page() + 1) # go to the next page
except:
error(_("%s already exists, delete it first if you want to create a new one") % (self.targetname), False)
return
def init_intro_page(self):
self.text_page("AppImageAssistant " + __version__, "This assistant helps you to package an AppDir for distribution as an AppImage. It is part of AppImageKit. \n\nPlease see http://portablelinuxapps.org/forum for more information.")
self.chooser_page("Please select the AppDir")
self.runner_page(self.run1_func)
@threaded # For this to work, the gtk.gdk.threads_init() function must be called before the gtk.main() function
def run1_func(self, widget):
command = ["python", os.path.join(os.path.dirname(sys.argv[0]), "package"), self.appdir, self.targetname]
print "Running command:"
print command
gtk.gdk.threads_enter() # this must be called in a function that is decorated with @threaded
widget.run_command(command) # this is the long-running command
gtk.gdk.threads_leave() # this must be called in a function that is decorated with @threaded
class RunBox(vte.Terminal):
def __init__(self, assistant):
vte.Terminal.__init__(self)
self.connect('child-exited', self.run_command_done_callback)
self.assistant = assistant # the assistant is passed in here so that we can e.g., disable forward buttons
def run_command(self, command_list):
self.assistant.set_page_complete(self.assistant.get_nth_page(self.assistant.get_current_page()), False)
self.thread_running = True
command = command_list
pid = self.fork_command(command=command[0], argv=command, directory=os.getcwd())
while self.thread_running:
gtk.main_iteration()
def run_command_done_callback(self, terminal):
print('child done')
self.assistant.set_page_complete(self.assistant.get_nth_page(self.assistant.get_current_page()), True) # enable the next page button
self.assistant.result_page() # only now initialize the results page because it needs to check whether we succeeded
self.assistant.set_current_page(self.assistant.get_current_page() + 1) # go to the next page
self.thread_running = False
if __name__=="__main__":
A = Assistant()
A.show()
gtk.gdk.threads_init() # The gtk.gdk.threads_init() function must be called before the gtk.main() function
gtk.main()

View File

@ -0,0 +1,9 @@
[Desktop Entry]
Name=AppImageAssistant
Exec=AppImageAssistant
Icon=AppImageAssistant
Terminal=false
Type=Application
Categories=Development;
Comment=Turn AppDir into AppImage, part of AppImageKit
StartupNotify=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

11
AppImageAssistant 0.9.3/AppRun Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
#HERE="$(dirname "$(readlink -f "${0}")")"
##PYTHONPATH="${HERE}"/usr/share/pyshared:"$PYTHONPATH" LD_LIBRARY_PATH="${HERE}/usr/lib/:${LD_LIBRARY_PATH}" PATH="${HERE}/:${PATH}" exec "${HERE}"/AppImageAssistant $@
#cd $(dirname "${0}")
#EXEC=$(grep -m 1 -r Exec= ./*.desktop | cut -d "=" -f 2 | cut -d % -f 1)
#PYTHONPATH=./usr/share/pyshared:"$PYTHONPATH" LD_LIBRARY_PATH="./:./usr/lib/${LD_LIBRARY_PATH}" PATH="./:${PATH}" exec $EXEC $@
cd $(dirname "${0}")
EXEC=$(grep -m 1 -r Exec= ./*.desktop | cut -d "=" -f 2 | cut -d % -f 1)
PYTHONPATH=./usr/share/pyshared:"$PYTHONPATH" LD_LIBRARY_PATH="./:${LD_LIBRARY_PATH}" PATH="./:${PATH}" exec $EXEC $@

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,437 @@
#
# Kiwi: a Framework and Enhanced Widgets for Python
#
# Copyright (C) 2005-2007 Async Open Source
#
# This program 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 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 Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Author(s): Johan Dahlin <jdahlin@async.com.br>
#
import os
import gettext
import atk
import gtk
__all__ = ['error', 'info', 'messagedialog', 'warning', 'yesno', 'save',
'open', 'HIGAlertDialog', 'BaseDialog']
_ = lambda m: gettext.dgettext('kiwi', m)
_IMAGE_TYPES = {
gtk.MESSAGE_INFO: gtk.STOCK_DIALOG_INFO,
gtk.MESSAGE_WARNING : gtk.STOCK_DIALOG_WARNING,
gtk.MESSAGE_QUESTION : gtk.STOCK_DIALOG_QUESTION,
gtk.MESSAGE_ERROR : gtk.STOCK_DIALOG_ERROR,
}
_BUTTON_TYPES = {
gtk.BUTTONS_NONE: (),
gtk.BUTTONS_OK: (gtk.STOCK_OK, gtk.RESPONSE_OK,),
gtk.BUTTONS_CLOSE: (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,),
gtk.BUTTONS_CANCEL: (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,),
gtk.BUTTONS_YES_NO: (gtk.STOCK_NO, gtk.RESPONSE_NO,
gtk.STOCK_YES, gtk.RESPONSE_YES),
gtk.BUTTONS_OK_CANCEL: (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK)
}
class HIGAlertDialog(gtk.Dialog):
def __init__(self, parent, flags,
type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_NONE):
if not type in _IMAGE_TYPES:
raise TypeError(
"type must be one of: %s", ', '.join(_IMAGE_TYPES.keys()))
if not buttons in _BUTTON_TYPES:
raise TypeError(
"buttons be one of: %s", ', '.join(_BUTTON_TYPES.keys()))
gtk.Dialog.__init__(self, '', parent, flags)
self.set_border_width(5)
self.set_resizable(False)
self.set_has_separator(False)
# Some window managers (ION) displays a default title (???) if
# the specified one is empty, workaround this by setting it
# to a single space instead
self.set_title(" ")
self.set_skip_taskbar_hint(True)
self.vbox.set_spacing(14)
# It seems like get_accessible is not available on windows, go figure
if hasattr(self, 'get_accessible'):
self.get_accessible().set_role(atk.ROLE_ALERT)
self._primary_label = gtk.Label()
self._secondary_label = gtk.Label()
self._details_label = gtk.Label()
self._image = gtk.image_new_from_stock(_IMAGE_TYPES[type],
gtk.ICON_SIZE_DIALOG)
self._image.set_alignment(0.5, 0.0)
self._primary_label.set_use_markup(True)
for label in (self._primary_label, self._secondary_label,
self._details_label):
label.set_line_wrap(True)
label.set_selectable(True)
label.set_alignment(0.0, 0.5)
hbox = gtk.HBox(False, 12)
hbox.set_border_width(5)
hbox.pack_start(self._image, False, False)
vbox = gtk.VBox(False, 0)
hbox.pack_start(vbox, False, False)
vbox.pack_start(self._primary_label, False, False)
vbox.pack_start(self._secondary_label, False, False)
self._expander = gtk.expander_new_with_mnemonic(
_("Show more _details"))
self._expander.set_spacing(6)
self._expander.add(self._details_label)
vbox.pack_start(self._expander, False, False)
self.vbox.pack_start(hbox, False, False)
hbox.show_all()
self._expander.hide()
self.add_buttons(*_BUTTON_TYPES[buttons])
self.label_vbox = vbox
def set_primary(self, text):
self._primary_label.set_markup(
"<span weight=\"bold\" size=\"larger\">%s</span>" % text)
def set_secondary(self, text):
self._secondary_label.set_markup(text)
def set_details(self, text):
self._details_label.set_text(text)
self._expander.show()
def set_details_widget(self, widget):
self._expander.remove(self._details_label)
self._expander.add(widget)
widget.show()
self._expander.show()
class BaseDialog(gtk.Dialog):
def __init__(self, parent=None, title='', flags=0, buttons=()):
if parent and not isinstance(parent, gtk.Window):
raise TypeError("parent needs to be None or a gtk.Window subclass")
if not flags and parent:
flags &= (gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT)
gtk.Dialog.__init__(self, title=title, parent=parent,
flags=flags, buttons=buttons)
self.set_border_width(6)
self.set_has_separator(False)
self.vbox.set_spacing(6)
def messagedialog(dialog_type, short, long=None, parent=None,
buttons=gtk.BUTTONS_OK, default=-1):
"""Create and show a MessageDialog.
@param dialog_type: one of constants
- gtk.MESSAGE_INFO
- gtk.MESSAGE_WARNING
- gtk.MESSAGE_QUESTION
- gtk.MESSAGE_ERROR
@param short: A header text to be inserted in the dialog.
@param long: A long description of message.
@param parent: The parent widget of this dialog
@type parent: a gtk.Window subclass
@param buttons: The button type that the dialog will be display,
one of the constants:
- gtk.BUTTONS_NONE
- gtk.BUTTONS_OK
- gtk.BUTTONS_CLOSE
- gtk.BUTTONS_CANCEL
- gtk.BUTTONS_YES_NO
- gtk.BUTTONS_OK_CANCEL
or a tuple or 2-sized tuples representing label and response. If label
is a stock-id a stock icon will be displayed.
@param default: optional default response id
"""
if buttons in (gtk.BUTTONS_NONE, gtk.BUTTONS_OK, gtk.BUTTONS_CLOSE,
gtk.BUTTONS_CANCEL, gtk.BUTTONS_YES_NO,
gtk.BUTTONS_OK_CANCEL):
dialog_buttons = buttons
buttons = []
else:
if buttons is not None and type(buttons) != tuple:
raise TypeError(
"buttons must be a GtkButtonsTypes constant or a tuple")
dialog_buttons = gtk.BUTTONS_NONE
if parent and not isinstance(parent, gtk.Window):
raise TypeError("parent must be a gtk.Window subclass")
d = HIGAlertDialog(parent=parent, flags=gtk.DIALOG_MODAL,
type=dialog_type, buttons=dialog_buttons)
if buttons:
for text, response in buttons:
d.add_buttons(text, response)
d.set_primary(short)
if long:
if isinstance(long, gtk.Widget):
d.set_details_widget(long)
elif isinstance(long, basestring):
d.set_details(long)
else:
raise TypeError(
"long must be a gtk.Widget or a string, not %r" % long)
if default != -1:
d.set_default_response(default)
if parent:
d.set_transient_for(parent)
d.set_modal(True)
response = d.run()
d.destroy()
return response
def _simple(type, short, long=None, parent=None, buttons=gtk.BUTTONS_OK,
default=-1):
if buttons == gtk.BUTTONS_OK:
default = gtk.RESPONSE_OK
return messagedialog(type, short, long,
parent=parent, buttons=buttons,
default=default)
def error(short, long=None, parent=None, buttons=gtk.BUTTONS_OK, default=-1):
return _simple(gtk.MESSAGE_ERROR, short, long, parent=parent,
buttons=buttons, default=default)
def info(short, long=None, parent=None, buttons=gtk.BUTTONS_OK, default=-1):
return _simple(gtk.MESSAGE_INFO, short, long, parent=parent,
buttons=buttons, default=default)
def warning(short, long=None, parent=None, buttons=gtk.BUTTONS_OK, default=-1):
return _simple(gtk.MESSAGE_WARNING, short, long, parent=parent,
buttons=buttons, default=default)
def yesno(text, parent=None, default=gtk.RESPONSE_YES,
buttons=gtk.BUTTONS_YES_NO):
return messagedialog(gtk.MESSAGE_WARNING, text, None, parent,
buttons=buttons, default=default)
def open(title='', parent=None, patterns=None, folder=None, filter=None):
"""Displays an open dialog.
@param title: the title of the folder, defaults to 'Select folder'
@param parent: parent gtk.Window or None
@param patterns: a list of pattern strings ['*.py', '*.pl'] or None
@param folder: initial folder or None
@param filter: a filter to use or None, is incompatible with patterns
"""
ffilter = filter
if patterns and ffilter:
raise TypeError("Can't use patterns and filter at the same time")
filechooser = gtk.FileChooserDialog(title or _('Open'),
parent,
gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN, gtk.RESPONSE_OK))
if patterns or ffilter:
if not ffilter:
ffilter = gtk.FileFilter()
for pattern in patterns:
ffilter.add_pattern(pattern)
filechooser.set_filter(ffilter)
filechooser.set_default_response(gtk.RESPONSE_OK)
if folder:
filechooser.set_current_folder(folder)
response = filechooser.run()
if response != gtk.RESPONSE_OK:
filechooser.destroy()
return
path = filechooser.get_filename()
if path and os.access(path, os.R_OK):
filechooser.destroy()
return path
abspath = os.path.abspath(path)
error(_('Could not open file "%s"') % abspath,
_('The file "%s" could not be opened. '
'Permission denied.') % abspath)
filechooser.destroy()
return
def selectfolder(title='', parent=None, folder=None):
"""Displays a select folder dialog.
@param title: the title of the folder, defaults to 'Select folder'
@param parent: parent gtk.Window or None
@param folder: initial folder or None
"""
filechooser = gtk.FileChooserDialog(
title or _('Select folder'),
parent,
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK))
if folder:
filechooser.set_current_folder(folder)
filechooser.set_default_response(gtk.RESPONSE_OK)
response = filechooser.run()
if response != gtk.RESPONSE_OK:
filechooser.destroy()
return
path = filechooser.get_filename()
if path and os.access(path, os.R_OK | os.X_OK):
filechooser.destroy()
return path
abspath = os.path.abspath(path)
error(_('Could not select folder "%s"') % abspath,
_('The folder "%s" could not be selected. '
'Permission denied.') % abspath)
filechooser.destroy()
return
def ask_overwrite(filename, parent=None):
submsg1 = _('A file named "%s" already exists') % os.path.abspath(filename)
submsg2 = _('Do you wish to replace it with the current one?')
text = ('<span weight="bold" size="larger">%s</span>\n\n%s\n'
% (submsg1, submsg2))
result = messagedialog(gtk.MESSAGE_ERROR, text, parent=parent,
buttons=((gtk.STOCK_CANCEL,
gtk.RESPONSE_CANCEL),
(_("Replace"),
gtk.RESPONSE_YES)))
return result == gtk.RESPONSE_YES
def save(title='', parent=None, current_name='', folder=None):
"""Displays a save dialog."""
filechooser = gtk.FileChooserDialog(title or _('Save'),
parent,
gtk.FILE_CHOOSER_ACTION_SAVE,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_SAVE, gtk.RESPONSE_OK))
if current_name:
filechooser.set_current_name(current_name)
filechooser.set_default_response(gtk.RESPONSE_OK)
if folder:
filechooser.set_current_folder(folder)
path = None
while True:
response = filechooser.run()
if response != gtk.RESPONSE_OK:
path = None
break
path = filechooser.get_filename()
if not os.path.exists(path):
break
if ask_overwrite(path, parent):
break
filechooser.destroy()
return path
def password(primary='', secondary='', parent=None):
"""
Shows a password dialog and returns the password entered in the dialog
@param primary: primary text
@param secondary: secondary text
@param parent: a gtk.Window subclass or None
@returns: the password or None if none specified
@rtype: string or None
"""
if not primary:
raise ValueError("primary cannot be empty")
d = HIGAlertDialog(parent=parent, flags=gtk.DIALOG_MODAL,
type=gtk.MESSAGE_QUESTION,
buttons=gtk.BUTTONS_OK_CANCEL)
d.set_default_response(gtk.RESPONSE_OK)
d.set_primary(primary + '\n')
if secondary:
secondary += '\n'
d.set_secondary(secondary)
hbox = gtk.HBox()
hbox.set_border_width(6)
hbox.show()
d.label_vbox.pack_start(hbox)
label = gtk.Label(_('Password:'))
label.show()
hbox.pack_start(label, False, False)
entry = gtk.Entry()
entry.set_invisible_char(u'\u2022')
entry.set_visibility(False)
entry.show()
d.add_action_widget(entry, gtk.RESPONSE_OK)
# FIXME: Is there another way of connecting widget::activate to a response?
d.action_area.remove(entry)
hbox.pack_start(entry, True, True, 12)
response = d.run()
if response == gtk.RESPONSE_OK:
password = entry.get_text()
else:
password = None
d.destroy()
return password
def _test():
yesno('Kill?', default=gtk.RESPONSE_NO)
info('Some information displayed not too long\nbut not too short',
long=('foobar ba asdjaiosjd oiadjoisjaoi aksjdasdasd kajsdhakjsdh\n'
'askdjhaskjdha skjdhasdasdjkasldj alksdjalksjda lksdjalksdj\n'
'asdjaslkdj alksdj lkasjdlkjasldkj alksjdlkasjd jklsdjakls\n'
'ask;ldjaklsjdlkasjd alksdj laksjdlkasjd lkajs kjaslk jkl\n'),
default=gtk.RESPONSE_OK,
)
error('An error occurred', gtk.Button('Woho'))
error('Unable to mount the selected volume.',
'mount: can\'t find /media/cdrom0 in /etc/fstab or /etc/mtab')
print open(title='Open a file', patterns=['*.py'])
print save(title='Save a file', current_name='foobar.py')
print password('Administrator password',
'To be able to continue the wizard you need to enter the '
'administrator password for the database on host anthem')
print selectfolder()
if __name__ == '__main__':
_test()

View File

@ -0,0 +1,4 @@
#!/bin/bash
cd $(dirname "${0}")
NAME=$(basename "${1}")
LD_LIBRARY_PATH="./usr/lib/:${LD_LIBRARY_PATH}" PATH="./usr/bin:${PATH}" xorriso -indev "${1}" -osirrox on -extract / $HOME/Desktop/"${NAME}.AppDir"

105
AppImageAssistant 0.9.3/package Executable file
View File

@ -0,0 +1,105 @@
#!/usr/bin/env python
# /**************************************************************************
#
# Copyright (c) 2005-12 Simon Peter
#
# All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# **************************************************************************/
from __future__ import division
import os, sys
import subprocess
import xdgappdir
import commands
from locale import gettext as _
# Also search for dependency binaries and libraries next to myself
dependenciesdir = os.path.dirname(__file__) + "/usr/"
os.environ['PATH'] = dependenciesdir + "/bin:" + os.getenv('PATH')
# print os.environ['PATH']
lddp = os.getenv('LD_LIBRARY_PATH')
if lddp == None: lddp = ""
os.environ['LD_LIBRARY_PATH'] = dependenciesdir + "/lib:" + lddp
if len(sys.argv) == 2:
print "Assuming I should inject a new runtime"
destinationfile = os.path.realpath(sys.argv[1])
should_compress = False
elif len(sys.argv) < 3:
print("")
print("This packs a direcory")
print("")
print("Usage: %s sourcedir destinationfile" % (os.path.basename(sys.argv[0])))
print("")
exit(1)
else:
sourcedir = os.path.realpath(sys.argv[1])
destinationfile = os.path.realpath(sys.argv[2])
should_compress = True
if should_compress == True:
if not os.path.exists(sourcedir):
print("Directory not found: %s" % (sourcedir))
exit(1)
if should_compress == True:
H = xdgappdir.AppDirXdgHandler(sourcedir)
iconfile = H.get_icon_path_by_icon_name(H.get_icon_name_from_desktop_file(H.desktopfile))
if iconfile == None:
print "Icon could not be found based on information in desktop file, aborting"
exit(1)
print("Creating %s..." % (destinationfile))
if os.path.exists(destinationfile):
print _("Destination path already exists, exiting") # xorriso would append another session to a pre-existing image
exit(1)
# As great as xorriso is, as cryptic its usage is :-(
command = ["xorriso", "-dev",
destinationfile, "-padding", "0", "-map",
sourcedir, "/", "--", "-map", iconfile, "/.DirIcon",
"-zisofs", "level=9:block_size=128k", "-chown_r", "0",
"/", "--", "set_filter_r", "--zisofs", "/" ]
subprocess.Popen(command).communicate()
print "ok"
print("Embedding runtime...")
elf = os.path.realpath(os.path.dirname(__file__)) + "/runtime"
s = file(elf, 'r')
f = file(destinationfile, 'r+')
f.write(s.read())
f.close()
s.close()
print "ok"
print("Making %s executable..." % (destinationfile))
os.chmod(destinationfile, 0755)
print "ok"
filesize = int(os.stat(destinationfile).st_size)
print (_("Size: %f MB") % (filesize/1024/1024))

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python
import os, sys, fnmatch
AppDir = os.path.realpath(os.path.dirname(__file__))
modules = []
for root, dirs, files in os.walk(AppDir):
for fname in files:
fpath = os.path.join(root,fname)
if fnmatch.fnmatch(fpath, '*__init__.py'):
modules.append(os.path.dirname(os.path.dirname(fpath)))
if fnmatch.fnmatch(fpath, '*/_*.so'):
modules.append(os.path.dirname(fpath))
if fnmatch.fnmatch(fpath, '*python*.so'):
modules.append(os.path.dirname(fpath))
if fnmatch.fnmatch(fpath, '*.py'):
modules.append(os.path.dirname(fpath))
modules = set(modules)
for module in modules:
sys.path.append(module)
usage = """
E.g., if the main executable is in $APPDIR/usr/bin, then add to the head of it:
import os,sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
import portability
"""
if __name__=="__main__":
print ""
print("Put this file into the root of an AppDir, then import it to make all Python modules in the AppDir visible to the app")
print usage

BIN
AppImageAssistant 0.9.3/runtime Executable file

Binary file not shown.

View File

@ -0,0 +1,120 @@
#!/bin/bash
#
# Test an AppDir or AppImage on a given ISO or squashfs base system
#
set -e
# set -x
HERE=$(dirname $(readlink -f "${0}"))
export PATH=$HERE:$PATH
if [ "$1x" == "x" ] ; then
echo "Please specify a ISO or squashfs base system to run the AppImage on"
exit 1
fi
if [ "$2x" == "x" ] ; then
echo "Please specify an AppDir or AppImage to be run"
exit 1
fi
mkdir -p /tmp/unionfs/root
mkdir -p /tmp/unionfs/rw
mkdir -p /tmp/union
mkdir -p /tmp/iso
# If ISO was specified, then mount it and find contained filesystem
THEFS="$1"
if [ ${1: -4} == ".iso" ] ; then
ISO="$1"
mount -o loop,ro "$ISO" /tmp/iso
fi
# In case of Ubuntu-like ISOs
if [ -e /tmp/iso/casper/filesystem.squashfs ] ; then
THEFS=/tmp/iso/casper/filesystem.squashfs
mount "$THEFS" /tmp/unionfs/root -o loop,ro || exit 1
fi
# In case of Fedora-like ISOs
if [ -e /tmp/iso/LiveOS/squashfs.img ] ; then
mount -o loop,ro /tmp/iso/LiveOS/squashfs.img /tmp/iso/
THEFS=/tmp/iso/LiveOS/ext3fs.img || exit 1
mount "$THEFS" /tmp/unionfs/root -o loop,ro || exit 1
fi
trap atexit EXIT
atexit()
{ set +e
umount -l /tmp/union/var/lib/dbus 2>/dev/null
umount -l /tmp/union/etc/resolv.conf 2>/dev/null
umount -l /tmp/union/proc 2>/dev/null
umount -l /tmp/union/boot 2>/dev/null
# umount -l /tmp/union/automake 2>/dev/null # Puppy
umount -l /tmp/union 2>/dev/null
umount -l /tmp/unionfs/root 2>/dev/null
umount -l /tmp/iso 2>/dev/null
umount -l /tmp/iso 2>/dev/null
umount -l /tmp/iso 2>/dev/null
sudo killall unionfs-fuse
rm -r /tmp/unionfs/root
rm -r /tmp/unionfs/rw
rm -r /tmp/unionfs
rm -r /tmp/union
rm -r /tmp/iso
}
unionfs-fuse -o allow_other,use_ino,suid,dev,nonempty -ocow,chroot=/tmp/unionfs/,max_files=32768 /rw=RW:/root=RO /tmp/union
ls /tmp/union/boot >/dev/null && MNT=/boot
# ls /tmp/union/automake >/dev/null && MNT=/automake || echo "" # Puppy
if [ "x$MNT" == "x" ] ; then
echo "Could not find free mountpoint"
exit 1
fi
if [ -f "$2" ] ; then
mount "$2" /tmp/union/$MNT -o loop
elif [ -d "$2" ] ; then
mount "$2" /tmp/union/$MNT -o bind
fi
cat > /tmp/union/run.sh <<EOF
#!/bin/sh
cat /etc/*release
echo ""
rm -rf /etc/pango
mkdir -p /etc/pango
pango-querymodules > '/etc/pango/pango.modules' # otherwise only squares instead of text
[ -f /si-chroot ] && ln -s /lib/ld-lsb.so.3 /lib/ld-linux.so.2
echo ""
echo "===================================================="
echo ""
LD_LIBRARY_PATH=$MNT/usr/lib:$MNT/lib/:$LD_LIBRARY_PATH ldd $MNT/usr/bin/* $MNT/usr/lib/* 2>/dev/null | grep "not found" | sort | uniq
echo ""
echo "===================================================="
echo ""
export HOME="/root"
export LANG="en_EN.UTF-8"
# export QT_PLUGIN_PATH=./lib/qt4/plugins ###################### !!!
dbus-launch $MNT/AppRun || $MNT/AppRun
EOF
chmod a+x /tmp/union/run.sh
mount -t proc proc /tmp/union/proc
mount --bind /var/lib/dbus /tmp/union/var/lib/dbus
touch /tmp/union/etc/resolv.conf || echo ""
mount --bind /etc/resolv.conf /tmp/union/etc/resolv.conf
xhost local: # otherwise "cannot open display: :0.0"
echo ""
echo "===================================================="
echo ""
chroot /tmp/union/ /run.sh # $MNT/AppRun
echo ""
echo "===================================================="
echo ""
exit $?

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,97 @@
"""
This module is based on a rox module (LGPL):
http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/basedir.py?rev=1.9&view=log
The freedesktop.org Base Directory specification provides a way for
applications to locate shared data and configuration:
http://standards.freedesktop.org/basedir-spec/
(based on version 0.6)
This module can be used to load and save from and to these directories.
Typical usage:
from rox import basedir
for dir in basedir.load_config_paths('mydomain.org', 'MyProg', 'Options'):
print "Load settings from", dir
dir = basedir.save_config_path('mydomain.org', 'MyProg')
print >>file(os.path.join(dir, 'Options'), 'w'), "foo=2"
Note: see the rox.Options module for a higher-level API for managing options.
"""
from __future__ import generators
import os
_home = os.environ.get('HOME', '/')
xdg_data_home = os.environ.get('XDG_DATA_HOME',
os.path.join(_home, '.local', 'share'))
xdg_data_dirs = [xdg_data_home] + \
os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
os.path.join(_home, '.config'))
xdg_config_dirs = [xdg_config_home] + \
os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':')
xdg_cache_home = os.environ.get('XDG_CACHE_HOME',
os.path.join(_home, '.cache'))
xdg_data_dirs = filter(lambda x: x, xdg_data_dirs)
xdg_config_dirs = filter(lambda x: x, xdg_config_dirs)
def save_config_path(*resource):
"""Ensure $XDG_CONFIG_HOME/<resource>/ exists, and return its path.
'resource' should normally be the name of your application. Use this
when SAVING configuration settings. Use the xdg_config_dirs variable
for loading."""
resource = os.path.join(*resource)
assert not resource.startswith('/')
path = os.path.join(xdg_config_home, resource)
if not os.path.isdir(path):
os.makedirs(path, 0700)
return path
def save_data_path(*resource):
"""Ensure $XDG_DATA_HOME/<resource>/ exists, and return its path.
'resource' is the name of some shared resource. Use this when updating
a shared (between programs) database. Use the xdg_data_dirs variable
for loading."""
resource = os.path.join(*resource)
assert not resource.startswith('/')
path = os.path.join(xdg_data_home, resource)
if not os.path.isdir(path):
os.makedirs(path)
return path
def load_config_paths(*resource):
"""Returns an iterator which gives each directory named 'resource' in the
configuration search path. Information provided by earlier directories should
take precedence over later ones (ie, the user's config dir comes first)."""
resource = os.path.join(*resource)
for config_dir in xdg_config_dirs:
path = os.path.join(config_dir, resource)
if os.path.exists(path): yield path
def load_first_config(*resource):
"""Returns the first result from load_config_paths, or None if there is nothing
to load."""
for x in load_config_paths(*resource):
return x
return None
def load_data_paths(*resource):
"""Returns an iterator which gives each directory named 'resource' in the
shared data search path. Information provided by earlier directories should
take precedence over later ones."""
resource = os.path.join(*resource)
for data_dir in xdg_data_dirs:
path = os.path.join(data_dir, resource)
if os.path.exists(path): yield path

View File

@ -0,0 +1,39 @@
"""
Functions to configure Basic Settings
"""
language = "C"
windowmanager = None
icon_theme = "highcolor"
icon_size = 48
cache_time = 5
root_mode = False
def setWindowManager(wm):
global windowmanager
windowmanager = wm
def setIconTheme(theme):
global icon_theme
icon_theme = theme
import xdg.IconTheme
xdg.IconTheme.themes = []
def setIconSize(size):
global icon_size
icon_size = size
def setCacheTime(time):
global cache_time
cache_time = time
def setLocale(lang):
import locale
lang = locale.normalize(lang)
locale.setlocale(locale.LC_ALL, lang)
import xdg.Locale
xdg.Locale.update(lang)
def setRootMode(boolean):
global root_mode
root_mode = boolean

View File

@ -0,0 +1,383 @@
"""
Complete implementation of the XDG Desktop Entry Specification Version 0.9.4
http://standards.freedesktop.org/desktop-entry-spec/
Not supported:
- Encoding: Legacy Mixed
- Does not check exec parameters
- Does not check URL's
- Does not completly validate deprecated/kde items
- Does not completly check categories
"""
from xdg.IniFile import *
from xdg.BaseDirectory import *
import os.path
class DesktopEntry(IniFile):
"Class to parse and validate DesktopEntries"
defaultGroup = 'Desktop Entry'
def __init__(self, filename=None):
self.content = dict()
if filename and os.path.exists(filename):
self.parse(filename)
elif filename:
self.new(filename)
def __str__(self):
return self.getName()
def parse(self, file):
IniFile.parse(self, file, ["Desktop Entry", "KDE Desktop Entry"])
# start standard keys
def getType(self):
return self.get('Type')
def getVersion(self):
return self.get('Version', type="numeric")
def getEncoding(self):
return self.get('Encoding')
def getName(self):
return self.get('Name', locale=True)
def getGenericName(self):
return self.get('GenericName', locale=True)
def getComment(self):
return self.get('Comment', locale=True)
def getNoDisplay(self):
return self.get('NoDisplay', type="boolean")
def getIcon(self):
return self.get('Icon', locale=True)
def getHidden(self):
return self.get('Hidden', type="boolean")
def getFilePattern(self):
return self.get('FilePattern', type="regex")
def getTryExec(self):
return self.get('TryExec')
def getExec(self):
return self.get('Exec')
def getPath(self):
return self.get('Path')
def getTerminal(self):
return self.get('Terminal', type="boolean")
def getSwallowTitle(self):
return self.get('SwallowTitle', locale=True)
def getSwallowExec(self):
return self.get('SwallowExec')
def getActions(self):
return self.get('Actions', list=True)
""" @deprecated, use getMimeTypes instead """
def getMimeType(self):
return self.get('MimeType', list=True, type="regex")
def getMimeTypes(self):
return self.get('MimeType', list=True)
def getSortOrder(self):
return self.get('SortOrder', list=True)
def getDev(self):
return self.get('Dev')
def getFSType(self):
return self.get('FSType')
def getMountPoint(self):
return self.get('MountPoint')
def getReadonly(self):
return self.get('ReadOnly', type="boolean")
def getUnmountIcon(self):
return self.get('UnmountIcon', locale=True)
def getURL(self):
return self.get('URL')
def getCategories(self):
return self.get('Categories', list=True)
def getOnlyShowIn(self):
return self.get('OnlyShowIn', list=True)
def getNotShowIn(self):
return self.get('NotShowIn', list=True)
def getStartupNotify(self):
return self.get('StartupNotify', type="boolean")
def getStartupWMClass(self):
return self.get('StartupWMClass')
# end standard keys
# start kde keys
def getServiceTypes(self):
return self.get('ServiceTypes', list=True)
def getDocPath(self):
return self.get('DocPath')
def getKeywords(self):
return self.get('Keywords', list=True, locale=True)
def getInitialPreference(self):
return self.get('InitialPreference')
# end kde keys
# start deprecated keys
def getMiniIcon(self):
return self.get('MiniIcon', locale=True)
def getTerminalOptions(self):
return self.get('TerminalOptions')
def getDefaultApp(self):
return self.get('DefaultApp')
def getProtocols(self):
return self.get('Protocols', list=True)
def getExtensions(self):
return self.get('Extensions', list=True)
def getBinaryPattern(self):
return self.get('BinaryPattern')
def getMapNotify(self):
return self.get('MapNotify')
# end deprecated keys
# desktop entry edit stuff
def new(self, filename):
if os.path.splitext(filename)[1] == ".desktop":
type = "Application"
elif os.path.splitext(filename)[1] == ".directory":
type = "Directory"
self.content = dict()
self.addGroup(self.defaultGroup)
self.set("Encoding", "UTF-8")
self.set("Type", type)
self.filename = filename
# end desktop entry edit stuff
# validation stuff
def checkExtras(self):
# header
if self.defaultGroup == "KDE Desktop Entry":
self.warnings.append('[KDE Desktop Entry]-Header is deprecated')
# file extension
if self.fileExtension == ".kdelnk":
self.warnings.append("File extension .kdelnk is deprecated")
elif self.fileExtension != ".desktop" and self.fileExtension != ".directory":
self.warnings.append('Unknown File extension')
# Type
try:
self.type = self.content[self.defaultGroup]["Type"]
except KeyError:
self.errors.append("Key 'Type' is missing")
# Encoding
try:
self.encoding = self.content[self.defaultGroup]["Encoding"]
except KeyError:
self.errors.append("Key 'Encoding' is missing")
# Version
try:
self.version = self.content[self.defaultGroup]["Version"]
except KeyError:
self.warnings.append("Key 'Version' is missing")
# Name
try:
self.name = self.content[self.defaultGroup]["Name"]
except KeyError:
self.errors.append("Key 'Name' is missing")
def checkGroup(self, group):
# check if group header is valid
if not (group == self.defaultGroup \
or re.match("^\Desktop Action [a-zA-Z]+\$", group) \
or (re.match("^\X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group)):
self.errors.append("Invalid Group name: %s" % group)
else:
#OnlyShowIn and NotShowIn
if self.content[group].has_key("OnlyShowIn") and self.content[group].has_key("NotShowIn"):
self.errors.append("Group may either have OnlyShowIn or NotShowIn, but not both")
def checkKey(self, key, value, group):
# standard keys
if key == "Type":
if value == "ServiceType" or value == "Service":
self.warnings.append("Type=%s is a KDE extension" % key)
elif value == "MimeType":
self.warnings.append("Type=MimeType is deprecated")
elif not (value == "Application" or value == "Link" or value == "FSDevice" or value == "Directory"):
self.errors.append("Value of key 'Type' must be Application, Link, FSDevice or Directory, but is '%s'" % value)
if self.fileExtension == ".directory" and not value == "Directory":
self.warnings.append("File extension is .directory, but Type is '%s'" % value)
elif self.fileExtension == ".desktop" and value == "Directory":
self.warnings.append("Files with Type=Directory should have the extension .directory")
elif key == "Version":
self.checkValue(key, value, type="number")
elif key == "Encoding":
if value == "Legacy-Mixed":
self.errors.append("Encoding=Legacy-Mixed is deprecated and not supported by this parser")
elif not value == "UTF-8":
self.errors.append("Value of key 'Encoding' must be UTF-8")
elif re.match("^Name"+xdg.Locale.regex+"$", key):
pass # locale string
elif re.match("^GenericName"+xdg.Locale.regex+"$", key):
pass # locale string
elif re.match("^Comment"+xdg.Locale.regex+"$", key):
pass # locale string
elif key == "NoDisplay":
self.checkValue(key, value, type="boolean")
elif key == "Hidden":
self.checkValue(key, value, type="boolean")
elif key == "Terminal":
self.checkValue(key, value, type="boolean")
self.checkType(key, "Application")
elif key == "TryExec":
self.checkValue(key, value)
self.checkType(key, "Application")
elif key == "Exec":
self.checkValue(key, value)
self.checkType(key, "Application")
elif key == "Path":
self.checkValue(key, value)
self.checkType(key, "Application")
elif re.match("^Icon"+xdg.Locale.regex+"$", key):
self.checkValue(key, value)
elif re.match("^SwallowTitle"+xdg.Locale.regex+"$", key):
self.checkType(key, "Application")
elif key == "SwallowExec":
self.checkValue(key, value)
self.checkType(key, "Application")
elif key == "FilePatterns":
self.checkValue(key, value, type="regex", list=True)
self.checkType(key, "Application")
elif key == "Actions":
self.checkValue(key, value, list=True)
self.checkType(key, "Application")
elif key == "MimeType":
self.checkValue(key, value, type="regex", list=True)
self.checkType(key, "Application")
elif key == "Categories":
self.checkValue(key, value)
self.checkType(key, "Application")
self.checkCategorie(value)
elif key == "OnlyShowIn":
self.checkValue(key, value, list=True)
self.checkOnlyShowIn(value)
elif key == "NotShowIn":
self.checkValue(key, value, list=True)
self.checkOnlyShowIn(value)
elif key == "StartupNotify":
self.checkValue(key, value, type="boolean")
self.checkType(key, "Application")
elif key == "StartupWMClass":
self.checkType(key, "Application")
elif key == "SortOrder":
self.checkValue(key, value, list=True)
self.checkType(key, "Directory")
elif key == "URL":
self.checkValue(key, value)
self.checkType(key, "URL")
elif key == "Dev":
self.checkValue(key, value)
self.checkType(key, "FSDevice")
elif key == "FSType":
self.checkValue(key, value)
self.checkType(key, "FSDevice")
elif key == "MountPoint":
self.checkValue(key, value)
self.checkType(key, "FSDevice")
elif re.match("^UnmountIcon"+xdg.Locale.regex+"$", key):
self.checkValue(key, value)
self.checkType(key, "FSDevice")
elif key == "ReadOnly":
self.checkValue(key, value, type="boolean")
self.checkType(key, "FSDevice")
# kde extensions
elif key == "ServiceTypes":
self.checkValue(key, value, list=True)
self.warnings.append("Key '%s' is a KDE extension" % key)
elif key == "DocPath":
self.checkValue(key, value)
self.warnings.append("Key '%s' is a KDE extension" % key)
elif re.match("^Keywords"+xdg.Locale.regex+"$", key):
self.checkValue(key, value, list=True)
self.warnings.append("Key '%s' is a KDE extension" % key)
elif key == "InitialPreference":
self.checkValue(key, value, type="number")
self.warnings.append("Key '%s' is a KDE extension" % key)
# deprecated keys
elif re.match("^MiniIcon"+xdg.Locale.regex+"$", key):
self.checkValue(key, value)
self.warnings.append("Key '%s' is deprecated" % key)
elif key == "TerminalOptions":
self.checkValue(key, value)
self.warnings.append("Key '%s' is deprecated" % key)
elif key == "DefaultApp":
self.checkValue(key, value)
self.warnings.append("Key '%s' is deprecated" % key)
elif key == "Protocols":
self.checkValue(key, value, list=True)
self.warnings.append("Key '%s' is deprecated" % key)
elif key == "Extensions":
self.checkValue(key, value, list=True)
self.warnings.append("Key '%s' is deprecated" % key)
elif key == "BinaryPattern":
self.checkValue(key, value)
self.warnings.append("Key '%s' is deprecated" % key)
elif key == "MapNotify":
self.checkValue(key, value)
self.warnings.append("Key '%s' is deprecated" % key)
# "X-" extensions
elif re.match("^X-[a-zA-Z0-9-]+", key):
pass
else:
self.errors.append("Invalid key: %s" % key)
def checkType(self, key, type):
if not self.getType() == type:
self.errors.append("Key '%s' only allowed in Type=%s" % (key, type))
def checkOnlyShowIn(self, value):
values = self.getList(value)
valid = ["GNOME", "KDE", "ROX", "XFCE", "Old"]
for item in values:
if item not in valid:
self.errors.append("'%s' is not a registered OnlyShowIn value" % item);
def checkCategorie(self, value):
values = self.getList(value)
valid = ["Legacy","Core","Development","Building","Debugger","IDE","GUIDesigner","Profiling","RevisionControl","Translation","Office","Calendar","ContactManagement","Database","Dictionary","Chart","Email","Finance","FlowChart","PDA","ProjectManagement","Presentation","Spreadsheet","WordProcessor","Graphics","2DGraphics","VectorGraphics","RasterGraphics","3DGraphics","Scanning","OCR","Photograph","Viewer","Settings","DesktopSettings","HardwareSettings","PackageManager","Network","Dialup","InstantMessaging","IRCClient","FileTransfer","HamRadio","News","P2P","RemoteAccess","Telephony","WebBrowser","WebDevelopment","AudioVideo","Audio","Midi","Mixer","Sequencer","Tuner","Video","TV","AudioVideoEditing","Player","Recorder","DiscBurning","Game","ActionGame","AdventureGame","ArcadeGame","BoardGame","BlocksGame","CardGame","KidsGame","LogicGame","RolePlaying","Simulation","SportsGame","StrategyGame","Education","Art","Art","Contruction","Music","Languages","Science","Astronomy","Biology","Chemistry","Geology","Math","MedicalSoftware","Physics","Teaching","Amusement","Applet","Archiving","Electronics","Emulator","Engineering","FileManager","Shell","Screensaver","TerminalEmulator","TrayIcon","System","Filesystem","Monitor","Security","Utility","Accessibility","Calculator","Clock","TextEditor","KDE","GNOME","GTK","Qt","Motif","Java","ConsoleOnly"]
for item in values:
if item not in valid:
self.errors.append("'%s' is not a registered Category" % item);

View File

@ -0,0 +1,51 @@
"""
Exception Classes for the xdg package
"""
debug = False
class Error(Exception):
def __init__(self, msg):
self.msg = msg
Exception.__init__(self, msg)
def __str__(self):
return self.msg
class ValidationError(Error):
def __init__(self, msg, file):
self.msg = msg
self.file = file
Error.__init__(self, "ValidationError in file '%s': %s " % (file, msg))
class ParsingError(Error):
def __init__(self, msg, file):
self.msg = msg
self.file = file
Error.__init__(self, "ParsingError in file '%s', %s" % (file, msg))
class NoKeyError(Error):
def __init__(self, key, group, file):
Error.__init__(self, "No key '%s' in group %s of file %s" % (key, group, file))
self.key = key
self.group = group
class DuplicateKeyError(Error):
def __init__(self, key, group, file):
Error.__init__(self, "Duplicate key '%s' in group %s of file %s" % (key, group, file))
self.key = key
self.group = group
class NoGroupError(Error):
def __init__(self, group, file):
Error.__init__(self, "No group: %s in file %s" % (group, file))
self.group = group
class DuplicateGroupError(Error):
def __init__(self, group, file):
Error.__init__(self, "Duplicate group: %s in file %s" % (group, file))
self.group = group
class NoThemeError(Error):
def __init__(self, theme):
Error.__init__(self, "No such icon-theme: %s" % theme)
self.theme = theme

View File

@ -0,0 +1,391 @@
"""
Complete implementation of the XDG Icon Spec Version 0.8
http://standards.freedesktop.org/icon-theme-spec/
"""
import os, sys, time
from xdg.IniFile import *
from xdg.BaseDirectory import *
from xdg.Exceptions import *
import xdg.Config
class IconTheme(IniFile):
"Class to parse and validate IconThemes"
def __init__(self):
IniFile.__init__(self)
def __repr__(self):
return self.name
def parse(self, file):
IniFile.parse(self, file, ["Icon Theme", "KDE Icon Theme"])
self.dir = os.path.dirname(file)
(nil, self.name) = os.path.split(self.dir)
def getDir(self):
return self.dir
# Standard Keys
def getName(self):
return self.get('Name', locale=True)
def getComment(self):
return self.get('Comment', locale=True)
def getInherits(self):
return self.get('Inherits', list=True)
def getDirectories(self):
return self.get('Directories', list=True)
def getHidden(self):
return self.get('Hidden', type="boolean")
def getExample(self):
return self.get('Example')
# Per Directory Keys
def getSize(self, directory):
return self.get('Size', type="integer", group=directory)
def getContext(self, directory):
return self.get('Context', group=directory)
def getType(self, directory):
value = self.get('Type', group=directory)
if value:
return value
else:
return "Threshold"
def getMaxSize(self, directory):
value = self.get('MaxSize', type="integer", group=directory)
if value or value == 0:
return value
else:
return self.getSize(directory)
def getMinSize(self, directory):
value = self.get('MinSize', type="integer", group=directory)
if value or value == 0:
return value
else:
return self.getSize(directory)
def getThreshold(self, directory):
value = self.get('Threshold', type="integer", group=directory)
if value or value == 0:
return value
else:
return 2
# validation stuff
def checkExtras(self):
# header
if self.defaultGroup == "KDE Icon Theme":
self.warnings.append('[KDE Icon Theme]-Header is deprecated')
# file extension
if self.fileExtension == ".theme":
pass
elif self.fileExtension == ".desktop":
self.warnings.append('.desktop fileExtension is deprecated')
else:
self.warnings.append('Unknown File extension')
# Check required keys
# Name
try:
self.name = self.content[self.defaultGroup]["Name"]
except KeyError:
self.errors.append("Key 'Name' is missing")
# Comment
try:
self.comment = self.content[self.defaultGroup]["Comment"]
except KeyError:
self.errors.append("Key 'Comment' is missing")
# Directories
try:
self.directories = self.content[self.defaultGroup]["Directories"]
except KeyError:
self.errors.append("Key 'Directories' is missing")
def checkGroup(self, group):
# check if group header is valid
if group == self.defaultGroup:
pass
elif group in self.getDirectories():
try:
self.type = self.content[group]["Type"]
except KeyError:
self.type = "Threshold"
try:
self.name = self.content[group]["Name"]
except KeyError:
self.errors.append("Key 'Name' in Group '%s' is missing" % group)
elif not (re.match("^\[X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group):
self.errors.append("Invalid Group name: %s" % group)
def checkKey(self, key, value, group):
# standard keys
if group == self.defaultGroup:
if re.match("^Name"+xdg.Locale.regex+"$", key):
pass
elif re.match("^Comment"+xdg.Locale.regex+"$", key):
pass
elif key == "Inherits":
self.checkValue(key, value, list=True)
elif key == "Directories":
self.checkValue(key, value, list=True)
elif key == "Hidden":
self.checkValue(key, value, type="boolean")
elif key == "Example":
self.checkValue(key, value)
elif re.match("^X-[a-zA-Z0-9-]+", key):
pass
else:
self.errors.append("Invalid key: %s" % key)
elif group in self.getDirectories():
if key == "Size":
self.checkValue(key, value, type="integer")
elif key == "Context":
self.checkValue(key, value)
elif key == "Type":
self.checkValue(key, value)
if value not in ["Fixed", "Scalable", "Threshold"]:
self.errors.append("Key 'Type' must be one out of 'Fixed','Scalable','Threshold', but is %s" % value)
elif key == "MaxSize":
self.checkValue(key, value, type="integer")
if self.type != "Scalable":
self.errors.append("Key 'MaxSize' give, but Type is %s" % self.type)
elif key == "MinSize":
self.checkValue(key, value, type="integer")
if self.type != "Scalable":
self.errors.append("Key 'MinSize' give, but Type is %s" % self.type)
elif key == "Threshold":
self.checkValue(key, value, type="integer")
if self.type != "Threshold":
self.errors.append("Key 'Threshold' give, but Type is %s" % self.type)
elif re.match("^X-[a-zA-Z0-9-]+", key):
pass
else:
self.errors.append("Invalid key: %s" % key)
class IconData(IniFile):
"Class to parse and validate IconData Files"
def __init__(self):
IniFile.__init__(self)
def __repr__(self):
return self.getDisplayName()
def parse(self, file):
IniFile.parse(self, file, ["Icon Data"])
# Standard Keys
def getDisplayName(self):
return self.get('DisplayName', locale=True)
def getEmbeddedTextRectangle(self):
return self.get('EmbeddedTextRectangle', list=True)
def getAttachPoints(self):
return self.get('AttachPoints', type="point", list=True)
# validation stuff
def checkExtras(self):
# file extension
if self.fileExtension != ".icon":
self.warnings.append('Unknown File extension')
def checkGroup(self, group):
# check if group header is valid
if not (group == self.defaultGroup \
or (re.match("^\[X-", group) and group.encode("ascii", 'ignore') == group)):
self.errors.append("Invalid Group name: %s" % group.encode("ascii", "replace"))
def checkKey(self, key, value, group):
# standard keys
if re.match("^DisplayName"+xdg.Locale.regex+"$", key):
pass
elif key == "EmbeddedTextRectangle":
self.checkValue(key, value, type="integer", list=True)
elif key == "AttachPoints":
self.checkValue(key, value, type="point", list=True)
elif re.match("^X-[a-zA-Z0-9-]+", key):
pass
else:
self.errors.append("Invalid key: %s" % key)
icondirs = []
for basedir in xdg_data_dirs:
icondirs.append(os.path.join(basedir, "icons"))
icondirs.append(os.path.join(basedir, "pixmaps"))
icondirs.append(os.path.expanduser("~/.icons"))
# just cache variables, they give a 10x speed improvement
themes = []
cache = dict()
dache = dict()
eache = dict()
def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg", "xpm"]):
global themes
if size == None:
size = xdg.Config.icon_size
if theme == None:
theme = xdg.Config.icon_theme
# if we have an absolute path, just return it
if os.path.isabs(iconname):
return iconname
# check if it has an extension and strip it
if os.path.splitext(iconname)[1][1:] in extensions:
iconname = os.path.splitext(iconname)[0]
# parse theme files
try:
if themes[0].name != theme:
themes = []
__addTheme(theme)
except IndexError:
__addTheme(theme)
# more caching (icon looked up in the last 5 seconds?)
tmp = "".join([iconname, str(size), theme, "".join(extensions)])
if eache.has_key(tmp):
if int(time.time() - eache[tmp][0]) >= xdg.Config.cache_time:
del eache[tmp]
else:
return eache[tmp][1]
for thme in themes:
icon = LookupIcon(iconname, size, thme, extensions)
if icon:
eache[tmp] = [time.time(), icon]
return icon
# cache stuff again (directories lookuped up in the last 5 seconds?)
for directory in icondirs:
if (not dache.has_key(directory) \
or (int(time.time() - dache[directory][1]) >= xdg.Config.cache_time \
and dache[directory][2] < os.path.getmtime(directory))) \
and os.path.isdir(directory):
dache[directory] = [os.listdir(directory), time.time(), os.path.getmtime(directory)]
for dir, values in dache.items():
for extension in extensions:
try:
if iconname + "." + extension in values[0]:
icon = os.path.join(dir, iconname + "." + extension)
eache[tmp] = [time.time(), icon]
return icon
except UnicodeDecodeError, e:
if debug:
raise e
else:
pass
# we haven't found anything? "hicolor" is our fallback
if theme != "hicolor":
icon = getIconPath(iconname, size, "hicolor")
eache[tmp] = [time.time(), icon]
return icon
def getIconData(path):
if os.path.isfile(path):
dirname = os.path.dirname(path)
basename = os.path.basename(path)
if os.path.isfile(os.path.join(dirname, basename + ".icon")):
data = IconData()
data.parse(os.path.join(dirname, basename + ".icon"))
return data
def __addTheme(theme):
for dir in icondirs:
if os.path.isfile(os.path.join(dir, theme, "index.theme")):
__parseTheme(os.path.join(dir,theme, "index.theme"))
break
elif os.path.isfile(os.path.join(dir, theme, "index.desktop")):
__parseTheme(os.path.join(dir,theme, "index.desktop"))
break
else:
if debug:
raise NoThemeError(theme)
def __parseTheme(file):
theme = IconTheme()
theme.parse(file)
themes.append(theme)
for subtheme in theme.getInherits():
__addTheme(subtheme)
def LookupIcon(iconname, size, theme, extensions):
# look for the cache
if not cache.has_key(theme.name):
cache[theme.name] = []
cache[theme.name].append(time.time() - (xdg.Config.cache_time + 1)) # [0] last time of lookup
cache[theme.name].append(0) # [1] mtime
cache[theme.name].append(dict()) # [2] dir: [subdir, [items]]
# cache stuff (directory lookuped up the in the last 5 seconds?)
if int(time.time() - cache[theme.name][0]) >= xdg.Config.cache_time:
cache[theme.name][0] = time.time()
for subdir in theme.getDirectories():
for directory in icondirs:
dir = os.path.join(directory,theme.name,subdir)
if (not cache[theme.name][2].has_key(dir) \
or cache[theme.name][1] < os.path.getmtime(os.path.join(directory,theme.name))) \
and subdir != "" \
and os.path.isdir(dir):
cache[theme.name][2][dir] = [subdir, os.listdir(dir)]
cache[theme.name][1] = os.path.getmtime(os.path.join(directory,theme.name))
for dir, values in cache[theme.name][2].items():
if DirectoryMatchesSize(values[0], size, theme):
for extension in extensions:
if iconname + "." + extension in values[1]:
return os.path.join(dir, iconname + "." + extension)
minimal_size = sys.maxint
closest_filename = ""
for dir, values in cache[theme.name][2].items():
distance = DirectorySizeDistance(values[0], size, theme)
if distance < minimal_size:
for extension in extensions:
if iconname + "." + extension in values[1]:
closest_filename = os.path.join(dir, iconname + "." + extension)
minimal_size = distance
return closest_filename
def DirectoryMatchesSize(subdir, iconsize, theme):
Type = theme.getType(subdir)
Size = theme.getSize(subdir)
Threshold = theme.getThreshold(subdir)
MinSize = theme.getMinSize(subdir)
MaxSize = theme.getMaxSize(subdir)
if Type == "Fixed":
return Size == iconsize
elif Type == "Scaleable":
return MinSize <= iconsize <= MaxSize
elif Type == "Threshold":
return Size - Threshold <= iconsize <= Size + Threshold
def DirectorySizeDistance(subdir, iconsize, theme):
Type = theme.getType(subdir)
Size = theme.getSize(subdir)
Threshold = theme.getThreshold(subdir)
MinSize = theme.getMinSize(subdir)
MaxSize = theme.getMaxSize(subdir)
if Type == "Fixed":
return abs(Size - iconsize)
elif Type == "Scalable":
if iconsize < MinSize:
return MinSize - iconsize
elif iconsize > MaxSize:
return MaxSize - iconsize
return 0
elif Type == "Threshold":
if iconsize < Size - Threshold:
return MinSize - iconsize
elif iconsize > Size + Threshold:
return iconsize - MaxSize
return 0

View File

@ -0,0 +1,402 @@
"""
Base Class for DesktopEntry, IconTheme and IconData
"""
import re, os.path, codecs
from Exceptions import *
import xdg.Locale
import gettext
class IniFile:
defaultGroup = ''
fileExtension = ''
filename = ''
gettext_domain = None
tainted = False
def __init__(self, filename=None):
self.content = dict()
if filename:
self.parse(filename)
def __cmp__(self, other):
return cmp(self.content, other.content)
def parse(self, filename, headers):
# for performance reasons
content = self.content
if not os.path.isfile(filename):
raise ParsingError("File not found", filename)
try:
fd = file(filename, 'r')
except IOError, e:
if debug:
raise e
else:
return
# parse file
for line in fd:
line = line.strip()
# empty line
if not line:
continue
# comment
elif line[0] == '#':
continue
# new group
elif line[0] == '[':
currentGroup = line.lstrip("[").rstrip("]")
if debug and self.hasGroup(currentGroup):
raise DuplicateGroupError(currentGroup, filename)
else:
content[currentGroup] = {}
# key
else:
index = line.find("=")
key = line[0:index].strip()
value = line[index+1:].strip()
try:
if debug and self.hasKey(key, currentGroup):
raise DuplicateKeyError(key, currentGroup, filename)
else:
content[currentGroup][key] = value
except (IndexError, UnboundLocalError):
raise ParsingError("[%s]-Header missing" % headers[0], filename)
fd.close()
self.filename = filename
self.tainted = False
# check header
for header in headers:
if content.has_key(header):
self.defaultGroup = header
break
else:
raise ParsingError("[%s]-Header missing" % headers[0], filename)
# check for gettext domain
e = self.content.get('Desktop Entry', {})
self.gettext_domain = e.get('X-GNOME-Gettext-Domain',
e.get('X-Ubuntu-Gettext-Domain', None))
# start stuff to access the keys
def get(self, key, group=None, locale=False, type="string", list=False):
# set default group
if not group:
group = self.defaultGroup
# return key (with locale)
if self.content.has_key(group) and self.content[group].has_key(key):
if locale:
key = self.__addLocale(key, group)
if key.endswith(']') or not self.gettext_domain:
# inline translations
value = self.content[group][key]
else:
value = gettext.dgettext(self.gettext_domain, self.content[group][key])
else:
value = self.content[group][key]
else:
if debug:
if not self.content.has_key(group):
raise NoGroupError(group, self.filename)
elif not self.content[group].has_key(key):
raise NoKeyError(key, group, self.filename)
else:
value = ""
if list == True:
values = self.getList(value)
result = []
else:
values = [value]
for value in values:
if type == "string" and locale == True:
value = value.decode("utf-8", "ignore")
elif type == "boolean":
value = self.__getBoolean(value)
elif type == "integer":
try:
value = int(value)
except ValueError:
value = 0
elif type == "numeric":
try:
value = float(value)
except ValueError:
value = 0.0
elif type == "regex":
value = re.compile(value)
elif type == "point":
value = value.split(",")
if list == True:
result.append(value)
else:
result = value
return result
# end stuff to access the keys
# start subget
def getList(self, string):
if re.search(r"(?<!\\)\;", string):
list = re.split(r"(?<!\\);", string)
elif re.search(r"(?<!\\)\|", string):
list = re.split(r"(?<!\\)\|", string)
elif re.search(r"(?<!\\),", string):
list = re.split(r"(?<!\\),", string)
else:
list = [string]
if list[-1] == "":
list.pop()
return list
def __getBoolean(self, boolean):
if boolean == 1 or boolean == "true" or boolean == "True":
return True
elif boolean == 0 or boolean == "false" or boolean == "False":
return False
return False
# end subget
def __addLocale(self, key, group=None):
"add locale to key according the current lc_messages"
# set default group
if not group:
group = self.defaultGroup
for lang in xdg.Locale.langs:
if self.content[group].has_key(key+'['+lang+']'):
return key+'['+lang+']'
return key
# start validation stuff
def validate(self, report="All"):
"validate ... report = All / Warnings / Errors"
self.warnings = []
self.errors = []
# get file extension
self.fileExtension = os.path.splitext(self.filename)[1]
# overwrite this for own checkings
self.checkExtras()
# check all keys
for group in self.content:
self.checkGroup(group)
for key in self.content[group]:
self.checkKey(key, self.content[group][key], group)
# check if value is empty
if self.content[group][key] == "":
self.warnings.append("Value of Key '%s' is empty" % key)
# raise Warnings / Errors
msg = ""
if report == "All" or report == "Warnings":
for line in self.warnings:
msg += "\n- " + line
if report == "All" or report == "Errors":
for line in self.errors:
msg += "\n- " + line
if msg:
raise ValidationError(msg, self.filename)
# check if group header is valid
def checkGroup(self, group):
pass
# check if key is valid
def checkKey(self, key, value, group):
pass
# check random stuff
def checkValue(self, key, value, type="string", list=False):
if list == True:
values = self.getList(value)
else:
values = [value]
for value in values:
if type == "string":
code = self.checkString(value)
elif type == "boolean":
code = self.checkBoolean(value)
elif type == "number":
code = self.checkNumber(value)
elif type == "integer":
code = self.checkInteger(value)
elif type == "regex":
code = self.checkRegex(value)
elif type == "point":
code = self.checkPoint(value)
if code == 1:
self.errors.append("'%s' is not a valid %s" % (value, type))
elif code == 2:
self.warnings.append("Value of key '%s' is deprecated" % key)
def checkExtras(self):
pass
def checkBoolean(self, value):
# 1 or 0 : deprecated
if (value == "1" or value == "0"):
return 2
# true or false: ok
elif not (value == "true" or value == "false"):
return 1
def checkNumber(self, value):
# float() ValueError
try:
float(value)
except:
return 1
def checkInteger(self, value):
# int() ValueError
try:
int(value)
except:
return 1
def checkPoint(self, value):
if not re.match("^[0-9]+,[0-9]+$", value):
return 1
def checkString(self, value):
# convert to ascii
if not value.decode("utf-8", "ignore").encode("ascii", 'ignore') == value:
return 1
def checkRegex(self, value):
try:
re.compile(value)
except:
return 1
# write support
def write(self, filename=None):
if not filename and not self.filename:
raise ParsingError("File not found", "")
if filename:
self.filename = filename
else:
filename = self.filename
if os.path.dirname(filename) and not os.path.isdir(os.path.dirname(filename)):
os.makedirs(os.path.dirname(filename))
fp = codecs.open(filename, 'w')
if self.defaultGroup:
fp.write("[%s]\n" % self.defaultGroup)
for (key, value) in self.content[self.defaultGroup].items():
fp.write("%s=%s\n" % (key, value))
fp.write("\n")
for (name, group) in self.content.items():
if name != self.defaultGroup:
fp.write("[%s]\n" % name)
for (key, value) in group.items():
fp.write("%s=%s\n" % (key, value))
fp.write("\n")
self.tainted = False
def set(self, key, value, group=None, locale=False):
# set default group
if not group:
group = self.defaultGroup
if locale == True and len(xdg.Locale.langs) > 0:
key = key + "[" + xdg.Locale.langs[0] + "]"
try:
if isinstance(value, unicode):
self.content[group][key] = value.encode("utf-8", "ignore")
else:
self.content[group][key] = value
except KeyError:
raise NoGroupError(group, self.filename)
self.tainted = (value == self.get(key, group))
def addGroup(self, group):
if self.hasGroup(group):
if debug:
raise DuplicateGroupError(group, self.filename)
else:
pass
else:
self.content[group] = {}
self.tainted = True
def removeGroup(self, group):
existed = group in self.content
if existed:
del self.content[group]
self.tainted = True
else:
if debug:
raise NoGroupError(group, self.filename)
return existed
def removeKey(self, key, group=None, locales=True):
# set default group
if not group:
group = self.defaultGroup
try:
if locales:
for (name, value) in self.content[group].items():
if re.match("^" + key + xdg.Locale.regex + "$", name) and name != key:
value = self.content[group][name]
del self.content[group][name]
value = self.content[group][key]
del self.content[group][key]
self.tainted = True
return value
except KeyError, e:
if debug:
if e == group:
raise NoGroupError(group, self.filename)
else:
raise NoKeyError(key, group, self.filename)
else:
return ""
# misc
def groups(self):
return self.content.keys()
def hasGroup(self, group):
if self.content.has_key(group):
return True
else:
return False
def hasKey(self, key, group=None):
# set default group
if not group:
group = self.defaultGroup
if self.content[group].has_key(key):
return True
else:
return False
def getFileName(self):
return self.filename

View File

@ -0,0 +1,79 @@
"""
Helper Module for Locale settings
This module is based on a ROX module (LGPL):
http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/i18n.py?rev=1.3&view=log
"""
import os
from locale import normalize
regex = "(\[([a-zA-Z]+)(_[a-zA-Z]+)?(\.[a-zA-Z\-0-9]+)?(@[a-zA-Z]+)?\])?"
def _expand_lang(locale):
locale = normalize(locale)
COMPONENT_CODESET = 1 << 0
COMPONENT_MODIFIER = 1 << 1
COMPONENT_TERRITORY = 1 << 2
# split up the locale into its base components
mask = 0
pos = locale.find('@')
if pos >= 0:
modifier = locale[pos:]
locale = locale[:pos]
mask |= COMPONENT_MODIFIER
else:
modifier = ''
pos = locale.find('.')
codeset = ''
if pos >= 0:
locale = locale[:pos]
pos = locale.find('_')
if pos >= 0:
territory = locale[pos:]
locale = locale[:pos]
mask |= COMPONENT_TERRITORY
else:
territory = ''
language = locale
ret = []
for i in range(mask+1):
if not (i & ~mask): # if all components for this combo exist ...
val = language
if i & COMPONENT_TERRITORY: val += territory
if i & COMPONENT_CODESET: val += codeset
if i & COMPONENT_MODIFIER: val += modifier
ret.append(val)
ret.reverse()
return ret
def expand_languages(languages=None):
# Get some reasonable defaults for arguments that were not supplied
if languages is None:
languages = []
for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
val = os.environ.get(envar)
if val:
languages = val.split(':')
break
#if 'C' not in languages:
# languages.append('C')
# now normalize and expand the languages
nelangs = []
for lang in languages:
for nelang in _expand_lang(lang):
if nelang not in nelangs:
nelangs.append(nelang)
return nelangs
def update(language=None):
global langs
if language:
langs = expand_languages([language])
else:
langs = expand_languages()
langs = []
update()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,511 @@
""" CLass to edit XDG Menus """
from xdg.Menu import *
from xdg.BaseDirectory import *
from xdg.Exceptions import *
from xdg.DesktopEntry import *
from xdg.Config import *
import xml.dom.minidom
import os
import re
# XML-Cleanups: Move / Exclude
# FIXME: proper reverte/delete
# FIXME: pass AppDirs/DirectoryDirs around in the edit/move functions
# FIXME: catch Exceptions
# FIXME: copy functions
# FIXME: More Layout stuff
# FIXME: unod/redo function / remove menu...
# FIXME: Advanced MenuEditing Stuff: LegacyDir/MergeFile
# Complex Rules/Deleted/OnlyAllocated/AppDirs/DirectoryDirs
class MenuEditor:
def __init__(self, menu=None, filename=None, root=False):
self.menu = None
self.filename = None
self.doc = None
self.parse(menu, filename, root)
# fix for creating two menus with the same name on the fly
self.filenames = []
def parse(self, menu=None, filename=None, root=False):
if root == True:
setRootMode(True)
if isinstance(menu, Menu):
self.menu = menu
elif menu:
self.menu = parse(menu)
else:
self.menu = parse()
if root == True:
self.filename = self.menu.Filename
elif filename:
self.filename = filename
else:
self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1])
try:
self.doc = xml.dom.minidom.parse(self.filename)
except IOError:
self.doc = xml.dom.minidom.parseString('<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://standards.freedesktop.org/menu-spec/menu-1.0.dtd"><Menu><Name>Applications</Name><MergeFile type="parent">'+self.menu.Filename+'</MergeFile></Menu>')
except xml.parsers.expat.ExpatError:
raise ParsingError('Not a valid .menu file', self.filename)
self.__remove_whilespace_nodes(self.doc)
def save(self):
self.__saveEntries(self.menu)
self.__saveMenu()
def createMenuEntry(self, parent, name, command=None, genericname=None, comment=None, icon=None, terminal=None, after=None, before=None):
menuentry = MenuEntry(self.__getFileName(name, ".desktop"))
menuentry = self.editMenuEntry(menuentry, name, genericname, comment, command, icon, terminal)
self.__addEntry(parent, menuentry, after, before)
sort(self.menu)
return menuentry
def createMenu(self, parent, name, genericname=None, comment=None, icon=None, after=None, before=None):
menu = Menu()
menu.Parent = parent
menu.Depth = parent.Depth + 1
menu.Layout = parent.DefaultLayout
menu.DefaultLayout = parent.DefaultLayout
menu = self.editMenu(menu, name, genericname, comment, icon)
self.__addEntry(parent, menu, after, before)
sort(self.menu)
return menu
def createSeparator(self, parent, after=None, before=None):
separator = Separator(parent)
self.__addEntry(parent, separator, after, before)
sort(self.menu)
return separator
def moveMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
self.__deleteEntry(oldparent, menuentry, after, before)
self.__addEntry(newparent, menuentry, after, before)
sort(self.menu)
return menuentry
def moveMenu(self, menu, oldparent, newparent, after=None, before=None):
self.__deleteEntry(oldparent, menu, after, before)
self.__addEntry(newparent, menu, after, before)
root_menu = self.__getXmlMenu(self.menu.Name)
if oldparent.getPath(True) != newparent.getPath(True):
self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name))
sort(self.menu)
return menu
def moveSeparator(self, separator, parent, after=None, before=None):
self.__deleteEntry(parent, separator, after, before)
self.__addEntry(parent, separator, after, before)
sort(self.menu)
return separator
def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
self.__addEntry(newparent, menuentry, after, before)
sort(self.menu)
return menuentry
def editMenuEntry(self, menuentry, name=None, genericname=None, comment=None, command=None, icon=None, terminal=None, nodisplay=None, hidden=None):
deskentry = menuentry.DesktopEntry
if name:
if not deskentry.hasKey("Name"):
deskentry.set("Name", name)
deskentry.set("Name", name, locale = True)
if comment:
if not deskentry.hasKey("Comment"):
deskentry.set("Comment", comment)
deskentry.set("Comment", comment, locale = True)
if genericname:
if not deskentry.hasKey("GnericNe"):
deskentry.set("GenericName", genericname)
deskentry.set("GenericName", genericname, locale = True)
if command:
deskentry.set("Exec", command)
if icon:
deskentry.set("Icon", icon)
if terminal == True:
deskentry.set("Terminal", "true")
elif terminal == False:
deskentry.set("Terminal", "false")
if nodisplay == True:
deskentry.set("NoDisplay", "true")
elif nodisplay == False:
deskentry.set("NoDisplay", "false")
if hidden == True:
deskentry.set("Hidden", "true")
elif hidden == False:
deskentry.set("Hidden", "false")
menuentry.updateAttributes()
if len(menuentry.Parents) > 0:
sort(self.menu)
return menuentry
def editMenu(self, menu, name=None, genericname=None, comment=None, icon=None, nodisplay=None, hidden=None):
# Hack for legacy dirs
if isinstance(menu.Directory, MenuEntry) and menu.Directory.Filename == ".directory":
xml_menu = self.__getXmlMenu(menu.getPath(True, True))
self.__addXmlTextElement(xml_menu, 'Directory', menu.Name + ".directory")
menu.Directory.setAttributes(menu.Name + ".directory")
# Hack for New Entries
elif not isinstance(menu.Directory, MenuEntry):
if not name:
name = menu.Name
filename = self.__getFileName(name, ".directory").replace("/", "")
if not menu.Name:
menu.Name = filename.replace(".directory", "")
xml_menu = self.__getXmlMenu(menu.getPath(True, True))
self.__addXmlTextElement(xml_menu, 'Directory', filename)
menu.Directory = MenuEntry(filename)
deskentry = menu.Directory.DesktopEntry
if name:
if not deskentry.hasKey("Name"):
deskentry.set("Name", name)
deskentry.set("Name", name, locale = True)
if genericname:
if not deskentry.hasKey("GenericName"):
deskentry.set("GenericName", genericname)
deskentry.set("GenericName", genericname, locale = True)
if comment:
if not deskentry.hasKey("Comment"):
deskentry.set("Comment", comment)
deskentry.set("Comment", comment, locale = True)
if icon:
deskentry.set("Icon", icon)
if nodisplay == True:
deskentry.set("NoDisplay", "true")
elif nodisplay == False:
deskentry.set("NoDisplay", "false")
if hidden == True:
deskentry.set("Hidden", "true")
elif hidden == False:
deskentry.set("Hidden", "false")
menu.Directory.updateAttributes()
if isinstance(menu.Parent, Menu):
sort(self.menu)
return menu
def hideMenuEntry(self, menuentry):
self.editMenuEntry(menuentry, nodisplay = True)
def unhideMenuEntry(self, menuentry):
self.editMenuEntry(menuentry, nodisplay = False, hidden = False)
def hideMenu(self, menu):
self.editMenu(menu, nodisplay = True)
def unhideMenu(self, menu):
self.editMenu(menu, nodisplay = False, hidden = False)
xml_menu = self.__getXmlMenu(menu.getPath(True,True), False)
for node in self.__getXmlNodesByName(["Deleted", "NotDeleted"], xml_menu):
node.parentNode.removeChild(node)
def deleteMenuEntry(self, menuentry):
if self.getAction(menuentry) == "delete":
self.__deleteFile(menuentry.DesktopEntry.filename)
for parent in menuentry.Parents:
self.__deleteEntry(parent, menuentry)
sort(self.menu)
return menuentry
def revertMenuEntry(self, menuentry):
if self.getAction(menuentry) == "revert":
self.__deleteFile(menuentry.DesktopEntry.filename)
menuentry.Original.Parents = []
for parent in menuentry.Parents:
index = parent.Entries.index(menuentry)
parent.Entries[index] = menuentry.Original
index = parent.MenuEntries.index(menuentry)
parent.MenuEntries[index] = menuentry.Original
menuentry.Original.Parents.append(parent)
sort(self.menu)
return menuentry
def deleteMenu(self, menu):
if self.getAction(menu) == "delete":
self.__deleteFile(menu.Directory.DesktopEntry.filename)
self.__deleteEntry(menu.Parent, menu)
xml_menu = self.__getXmlMenu(menu.getPath(True, True))
xml_menu.parentNode.removeChild(xml_menu)
sort(self.menu)
return menu
def revertMenu(self, menu):
if self.getAction(menu) == "revert":
self.__deleteFile(menu.Directory.DesktopEntry.filename)
menu.Directory = menu.Directory.Original
sort(self.menu)
return menu
def deleteSeparator(self, separator):
self.__deleteEntry(separator.Parent, separator, after=True)
sort(self.menu)
return separator
""" Private Stuff """
def getAction(self, entry):
if isinstance(entry, Menu):
if not isinstance(entry.Directory, MenuEntry):
return "none"
elif entry.Directory.getType() == "Both":
return "revert"
elif entry.Directory.getType() == "User" \
and (len(entry.Submenus) + len(entry.MenuEntries)) == 0:
return "delete"
elif isinstance(entry, MenuEntry):
if entry.getType() == "Both":
return "revert"
elif entry.getType() == "User":
return "delete"
else:
return "none"
return "none"
def __saveEntries(self, menu):
if not menu:
menu = self.menu
if isinstance(menu.Directory, MenuEntry):
menu.Directory.save()
for entry in menu.getEntries(hidden=True):
if isinstance(entry, MenuEntry):
entry.save()
elif isinstance(entry, Menu):
self.__saveEntries(entry)
def __saveMenu(self):
if not os.path.isdir(os.path.dirname(self.filename)):
os.makedirs(os.path.dirname(self.filename))
fd = open(self.filename, 'w')
fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*</", "\\1</", self.doc.toprettyxml().replace('<?xml version="1.0" ?>\n', '')))
fd.close()
def __getFileName(self, name, extension):
postfix = 0
while 1:
if postfix == 0:
filename = name + extension
else:
filename = name + "-" + str(postfix) + extension
if extension == ".desktop":
dir = "applications"
elif extension == ".directory":
dir = "desktop-directories"
if not filename in self.filenames and not \
os.path.isfile(os.path.join(xdg_data_dirs[0], dir, filename)):
self.filenames.append(filename)
break
else:
postfix += 1
return filename
def __getXmlMenu(self, path, create=True, element=None):
if not element:
element = self.doc
if "/" in path:
(name, path) = path.split("/", 1)
else:
name = path
path = ""
found = None
for node in self.__getXmlNodesByName("Menu", element):
for child in self.__getXmlNodesByName("Name", node):
if child.childNodes[0].nodeValue == name:
if path:
found = self.__getXmlMenu(path, create, node)
else:
found = node
break
if found:
break
if not found and create == True:
node = self.__addXmlMenuElement(element, name)
if path:
found = self.__getXmlMenu(path, create, node)
else:
found = node
return found
def __addXmlMenuElement(self, element, name):
node = self.doc.createElement('Menu')
self.__addXmlTextElement(node, 'Name', name)
return element.appendChild(node)
def __addXmlTextElement(self, element, name, text):
node = self.doc.createElement(name)
text = self.doc.createTextNode(text)
node.appendChild(text)
return element.appendChild(node)
def __addXmlFilename(self, element, filename, type = "Include"):
# remove old filenames
for node in self.__getXmlNodesByName(["Include", "Exclude"], element):
if node.childNodes[0].nodeName == "Filename" and node.childNodes[0].childNodes[0].nodeValue == filename:
element.removeChild(node)
# add new filename
node = self.doc.createElement(type)
node.appendChild(self.__addXmlTextElement(node, 'Filename', filename))
return element.appendChild(node)
def __addXmlMove(self, element, old, new):
node = self.doc.createElement("Move")
node.appendChild(self.__addXmlTextElement(node, 'Old', old))
node.appendChild(self.__addXmlTextElement(node, 'New', new))
return element.appendChild(node)
def __addXmlLayout(self, element, layout):
# remove old layout
for node in self.__getXmlNodesByName("Layout", element):
element.removeChild(node)
# add new layout
node = self.doc.createElement("Layout")
for order in layout.order:
if order[0] == "Separator":
child = self.doc.createElement("Separator")
node.appendChild(child)
elif order[0] == "Filename":
child = self.__addXmlTextElement(node, "Filename", order[1])
elif order[0] == "Menuname":
child = self.__addXmlTextElement(node, "Menuname", order[1])
elif order[0] == "Merge":
child = self.doc.createElement("Merge")
child.setAttribute("type", order[1])
node.appendChild(child)
return element.appendChild(node)
def __getXmlNodesByName(self, name, element):
for child in element.childNodes:
if child.nodeType == xml.dom.Node.ELEMENT_NODE and child.nodeName in name:
yield child
def __addLayout(self, parent):
layout = Layout()
layout.order = []
layout.show_empty = parent.Layout.show_empty
layout.inline = parent.Layout.inline
layout.inline_header = parent.Layout.inline_header
layout.inline_alias = parent.Layout.inline_alias
layout.inline_limit = parent.Layout.inline_limit
layout.order.append(["Merge", "menus"])
for entry in parent.Entries:
if isinstance(entry, Menu):
layout.parseMenuname(entry.Name)
elif isinstance(entry, MenuEntry):
layout.parseFilename(entry.DesktopFileID)
elif isinstance(entry, Separator):
layout.parseSeparator()
layout.order.append(["Merge", "files"])
parent.Layout = layout
return layout
def __addEntry(self, parent, entry, after=None, before=None):
if after or before:
if after:
index = parent.Entries.index(after) + 1
elif before:
index = parent.Entries.index(before)
parent.Entries.insert(index, entry)
else:
parent.Entries.append(entry)
xml_parent = self.__getXmlMenu(parent.getPath(True, True))
if isinstance(entry, MenuEntry):
parent.MenuEntries.append(entry)
entry.Parents.append(parent)
self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Include")
elif isinstance(entry, Menu):
parent.addSubmenu(entry)
if after or before:
self.__addLayout(parent)
self.__addXmlLayout(xml_parent, parent.Layout)
def __deleteEntry(self, parent, entry, after=None, before=None):
parent.Entries.remove(entry)
xml_parent = self.__getXmlMenu(parent.getPath(True, True))
if isinstance(entry, MenuEntry):
entry.Parents.remove(parent)
parent.MenuEntries.remove(entry)
self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Exclude")
elif isinstance(entry, Menu):
parent.Submenus.remove(entry)
if after or before:
self.__addLayout(parent)
self.__addXmlLayout(xml_parent, parent.Layout)
def __deleteFile(self, filename):
try:
os.remove(filename)
except OSError:
pass
try:
self.filenames.remove(filename)
except ValueError:
pass
def __remove_whilespace_nodes(self, node):
remove_list = []
for child in node.childNodes:
if child.nodeType == xml.dom.minidom.Node.TEXT_NODE:
child.data = child.data.strip()
if not child.data.strip():
remove_list.append(child)
elif child.hasChildNodes():
self.__remove_whilespace_nodes(child)
for node in remove_list:
node.parentNode.removeChild(node)

View File

@ -0,0 +1,474 @@
"""
This module is based on a rox module (LGPL):
http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/mime.py?rev=1.21&view=log
This module provides access to the shared MIME database.
types is a dictionary of all known MIME types, indexed by the type name, e.g.
types['application/x-python']
Applications can install information about MIME types by storing an
XML file as <MIME>/packages/<application>.xml and running the
update-mime-database command, which is provided by the freedesktop.org
shared mime database package.
See http://www.freedesktop.org/standards/shared-mime-info-spec/ for
information about the format of these files.
(based on version 0.13)
"""
import os
import stat
import fnmatch
import xdg.BaseDirectory
import xdg.Locale
from xml.dom import Node, minidom, XML_NAMESPACE
FREE_NS = 'http://www.freedesktop.org/standards/shared-mime-info'
types = {} # Maps MIME names to type objects
exts = None # Maps extensions to types
globs = None # List of (glob, type) pairs
literals = None # Maps liternal names to types
magic = None
def _get_node_data(node):
"""Get text of XML node"""
return ''.join([n.nodeValue for n in node.childNodes]).strip()
def lookup(media, subtype = None):
"Get the MIMEtype object for this type, creating a new one if needed."
if subtype is None and '/' in media:
media, subtype = media.split('/', 1)
if (media, subtype) not in types:
types[(media, subtype)] = MIMEtype(media, subtype)
return types[(media, subtype)]
class MIMEtype:
"""Type holding data about a MIME type"""
def __init__(self, media, subtype):
"Don't use this constructor directly; use mime.lookup() instead."
assert media and '/' not in media
assert subtype and '/' not in subtype
assert (media, subtype) not in types
self.media = media
self.subtype = subtype
self._comment = None
def _load(self):
"Loads comment for current language. Use get_comment() instead."
resource = os.path.join('mime', self.media, self.subtype + '.xml')
for path in xdg.BaseDirectory.load_data_paths(resource):
doc = minidom.parse(path)
if doc is None:
continue
for comment in doc.documentElement.getElementsByTagNameNS(FREE_NS, 'comment'):
lang = comment.getAttributeNS(XML_NAMESPACE, 'lang') or 'en'
goodness = 1 + (lang in xdg.Locale.langs)
if goodness > self._comment[0]:
self._comment = (goodness, _get_node_data(comment))
if goodness == 2: return
# FIXME: add get_icon method
def get_comment(self):
"""Returns comment for current language, loading it if needed."""
# Should we ever reload?
if self._comment is None:
self._comment = (0, str(self))
self._load()
return self._comment[1]
def __str__(self):
return self.media + '/' + self.subtype
def __repr__(self):
return '[%s: %s]' % (self, self._comment or '(comment not loaded)')
class MagicRule:
def __init__(self, f):
self.next=None
self.prev=None
#print line
ind=''
while True:
c=f.read(1)
if c=='>':
break
ind+=c
if not ind:
self.nest=0
else:
self.nest=int(ind)
start=''
while True:
c=f.read(1)
if c=='=':
break
start+=c
self.start=int(start)
hb=f.read(1)
lb=f.read(1)
self.lenvalue=ord(lb)+(ord(hb)<<8)
self.value=f.read(self.lenvalue)
c=f.read(1)
if c=='&':
self.mask=f.read(self.lenvalue)
c=f.read(1)
else:
self.mask=None
if c=='~':
w=''
while c!='+' and c!='\n':
c=f.read(1)
if c=='+' or c=='\n':
break
w+=c
self.word=int(w)
else:
self.word=1
if c=='+':
r=''
while c!='\n':
c=f.read(1)
if c=='\n':
break
r+=c
#print r
self.range=int(r)
else:
self.range=1
if c!='\n':
raise 'Malformed MIME magic line'
def getLength(self):
return self.start+self.lenvalue+self.range
def appendRule(self, rule):
if self.nest<rule.nest:
self.next=rule
rule.prev=self
elif self.prev:
self.prev.appendRule(rule)
def match(self, buffer):
if self.match0(buffer):
if self.next:
return self.next.match(buffer)
return True
def match0(self, buffer):
l=len(buffer)
for o in range(self.range):
s=self.start+o
e=s+self.lenvalue
if l<e:
return False
if self.mask:
test=''
for i in range(self.lenvalue):
c=ord(buffer[s+i]) & ord(self.mask[i])
test+=chr(c)
else:
test=buffer[s:e]
if test==self.value:
return True
def __repr__(self):
return '<MagicRule %d>%d=[%d]%s&%s~%d+%d>' % (self.nest,
self.start,
self.lenvalue,
`self.value`,
`self.mask`,
self.word,
self.range)
class MagicType:
def __init__(self, mtype):
self.mtype=mtype
self.top_rules=[]
self.last_rule=None
def getLine(self, f):
nrule=MagicRule(f)
if nrule.nest and self.last_rule:
self.last_rule.appendRule(nrule)
else:
self.top_rules.append(nrule)
self.last_rule=nrule
return nrule
def match(self, buffer):
for rule in self.top_rules:
if rule.match(buffer):
return self.mtype
def __repr__(self):
return '<MagicType %s>' % self.mtype
class MagicDB:
def __init__(self):
self.types={} # Indexed by priority, each entry is a list of type rules
self.maxlen=0
def mergeFile(self, fname):
f=file(fname, 'r')
line=f.readline()
if line!='MIME-Magic\0\n':
raise 'Not a MIME magic file'
while True:
shead=f.readline()
#print shead
if not shead:
break
if shead[0]!='[' or shead[-2:]!=']\n':
raise 'Malformed section heading'
pri, tname=shead[1:-2].split(':')
#print shead[1:-2]
pri=int(pri)
mtype=lookup(tname)
try:
ents=self.types[pri]
except:
ents=[]
self.types[pri]=ents
magictype=MagicType(mtype)
#print tname
#rline=f.readline()
c=f.read(1)
f.seek(-1, 1)
while c and c!='[':
rule=magictype.getLine(f)
#print rule
if rule and rule.getLength()>self.maxlen:
self.maxlen=rule.getLength()
c=f.read(1)
f.seek(-1, 1)
ents.append(magictype)
#self.types[pri]=ents
if not c:
break
def match_data(self, data, max_pri=100, min_pri=0):
pris=self.types.keys()
pris.sort(lambda a, b: -cmp(a, b))
for pri in pris:
#print pri, max_pri, min_pri
if pri>max_pri:
continue
if pri<min_pri:
break
for type in self.types[pri]:
m=type.match(buf)
if m:
return m
def match(self, path, max_pri=100, min_pri=0):
try:
buf=file(path, 'r').read(self.maxlen)
return self.match_data(buf, max_pri, min_pri)
except:
pass
return None
def __repr__(self):
return '<MagicDB %s>' % self.types
# Some well-known types
text = lookup('text', 'plain')
inode_block = lookup('inode', 'blockdevice')
inode_char = lookup('inode', 'chardevice')
inode_dir = lookup('inode', 'directory')
inode_fifo = lookup('inode', 'fifo')
inode_socket = lookup('inode', 'socket')
inode_symlink = lookup('inode', 'symlink')
inode_door = lookup('inode', 'door')
app_exe = lookup('application', 'executable')
_cache_uptodate = False
def _cache_database():
global exts, globs, literals, magic, _cache_uptodate
_cache_uptodate = True
exts = {} # Maps extensions to types
globs = [] # List of (glob, type) pairs
literals = {} # Maps liternal names to types
magic = MagicDB()
def _import_glob_file(path):
"""Loads name matching information from a MIME directory."""
for line in file(path):
if line.startswith('#'): continue
line = line[:-1]
type_name, pattern = line.split(':', 1)
mtype = lookup(type_name)
if pattern.startswith('*.'):
rest = pattern[2:]
if not ('*' in rest or '[' in rest or '?' in rest):
exts[rest] = mtype
continue
if '*' in pattern or '[' in pattern or '?' in pattern:
globs.append((pattern, mtype))
else:
literals[pattern] = mtype
for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'globs')):
_import_glob_file(path)
for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'magic')):
magic.mergeFile(path)
# Sort globs by length
globs.sort(lambda a, b: cmp(len(b[0]), len(a[0])))
def get_type_by_name(path):
"""Returns type of file by its name, or None if not known"""
if not _cache_uptodate:
_cache_database()
leaf = os.path.basename(path)
if leaf in literals:
return literals[leaf]
lleaf = leaf.lower()
if lleaf in literals:
return literals[lleaf]
ext = leaf
while 1:
p = ext.find('.')
if p < 0: break
ext = ext[p + 1:]
if ext in exts:
return exts[ext]
ext = lleaf
while 1:
p = ext.find('.')
if p < 0: break
ext = ext[p+1:]
if ext in exts:
return exts[ext]
for (glob, mime_type) in globs:
if fnmatch.fnmatch(leaf, glob):
return mime_type
if fnmatch.fnmatch(lleaf, glob):
return mime_type
return None
def get_type_by_contents(path, max_pri=100, min_pri=0):
"""Returns type of file by its contents, or None if not known"""
if not _cache_uptodate:
_cache_database()
return magic.match(path, max_pri, min_pri)
def get_type_by_data(data, max_pri=100, min_pri=0):
"""Returns type of the data"""
if not _cache_uptodate:
_cache_database()
return magic.match_data(data, max_pri, min_pri)
def get_type(path, follow=1, name_pri=100):
"""Returns type of file indicated by path.
path - pathname to check (need not exist)
follow - when reading file, follow symbolic links
name_pri - Priority to do name matches. 100=override magic"""
if not _cache_uptodate:
_cache_database()
try:
if follow:
st = os.stat(path)
else:
st = os.lstat(path)
except:
t = get_type_by_name(path)
return t or text
if stat.S_ISREG(st.st_mode):
t = get_type_by_contents(path, min_pri=name_pri)
if not t: t = get_type_by_name(path)
if not t: t = get_type_by_contents(path, max_pri=name_pri)
if t is None:
if stat.S_IMODE(st.st_mode) & 0111:
return app_exe
else:
return text
return t
elif stat.S_ISDIR(st.st_mode): return inode_dir
elif stat.S_ISCHR(st.st_mode): return inode_char
elif stat.S_ISBLK(st.st_mode): return inode_block
elif stat.S_ISFIFO(st.st_mode): return inode_fifo
elif stat.S_ISLNK(st.st_mode): return inode_symlink
elif stat.S_ISSOCK(st.st_mode): return inode_socket
return inode_door
def install_mime_info(application, package_file):
"""Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml.
If package_file is None, install <app_dir>/<application>.xml.
If already installed, does nothing. May overwrite an existing
file with the same name (if the contents are different)"""
application += '.xml'
new_data = file(package_file).read()
# See if the file is already installed
package_dir = os.path.join('mime', 'packages')
resource = os.path.join(package_dir, application)
for x in xdg.BaseDirectory.load_data_paths(resource):
try:
old_data = file(x).read()
except:
continue
if old_data == new_data:
return # Already installed
global _cache_uptodate
_cache_uptodate = False
# Not already installed; add a new copy
# Create the directory structure...
new_file = os.path.join(xdg.BaseDirectory.save_data_path(package_dir), application)
# Write the file...
file(new_file, 'w').write(new_data)
# Update the database...
command = 'update-mime-database'
if os.spawnlp(os.P_WAIT, command, command, xdg.BaseDirectory.save_data_path('mime')):
os.unlink(new_file)
raise Exception("The '%s' command returned an error code!\n" \
"Make sure you have the freedesktop.org shared MIME package:\n" \
"http://standards.freedesktop.org/shared-mime-info/") % command

View File

@ -0,0 +1,159 @@
"""
Implementation of the XDG Recent File Storage Specification Version 0.2
http://standards.freedesktop.org/recent-file-spec
"""
import xml.dom.minidom, xml.sax.saxutils
import os, time, fcntl
from xdg.Exceptions import *
class RecentFiles:
def __init__(self):
self.RecentFiles = []
self.filename = ""
def parse(self, filename=None):
if not filename:
filename = os.path.join(os.getenv("HOME"), ".recently-used")
try:
doc = xml.dom.minidom.parse(filename)
except IOError:
raise ParsingError('File not found', filename)
except xml.parsers.expat.ExpatError:
raise ParsingError('Not a valid .menu file', filename)
self.filename = filename
for child in doc.childNodes:
if child.nodeType == xml.dom.Node.ELEMENT_NODE:
if child.tagName == "RecentFiles":
for recent in child.childNodes:
if recent.nodeType == xml.dom.Node.ELEMENT_NODE:
if recent.tagName == "RecentItem":
self.__parseRecentItem(recent)
self.sort()
def __parseRecentItem(self, item):
recent = RecentFile()
self.RecentFiles.append(recent)
for attribute in item.childNodes:
if attribute.nodeType == xml.dom.Node.ELEMENT_NODE:
if attribute.tagName == "URI":
recent.URI = attribute.childNodes[0].nodeValue
elif attribute.tagName == "Mime-Type":
recent.MimeType = attribute.childNodes[0].nodeValue
elif attribute.tagName == "Timestamp":
recent.Timestamp = attribute.childNodes[0].nodeValue
elif attribute.tagName == "Private":
recent.Prviate = True
elif attribute.tagName == "Groups":
for group in attribute.childNodes:
if group.nodeType == xml.dom.Node.ELEMENT_NODE:
if group.tagName == "Group":
recent.Groups.append(group.childNodes[0].nodeValue)
def write(self, filename=None):
if not filename and not self.filename:
raise ParsingError('File not found', filename)
elif not filename:
filename = self.filename
f = open(filename, "w")
fcntl.lockf(f, fcntl.LOCK_EX)
f.write('<?xml version="1.0"?>\n')
f.write("<RecentFiles>\n")
for r in self.RecentFiles:
f.write(" <RecentItem>\n")
f.write(" <URI>%s</URI>\n" % xml.sax.saxutils.escape(r.URI))
f.write(" <Mime-Type>%s</Mime-Type>\n" % r.MimeType)
f.write(" <Timestamp>%s</Timestamp>\n" % r.Timestamp)
if r.Private == True:
f.write(" <Private/>\n")
if len(r.Groups) > 0:
f.write(" <Groups>\n")
for group in r.Groups:
f.write(" <Group>%s</Group>\n" % group)
f.write(" </Groups>\n")
f.write(" </RecentItem>\n")
f.write("</RecentFiles>\n")
fcntl.lockf(f, fcntl.LOCK_UN)
f.close()
def getFiles(self, mimetypes=None, groups=None, limit=0):
tmp = []
i = 0
for item in self.RecentFiles:
if groups:
for group in groups:
if group in item.Groups:
tmp.append(item)
i += 1
elif mimetypes:
for mimetype in mimetypes:
if mimetype == item.MimeType:
tmp.append(item)
i += 1
else:
if item.Private == False:
tmp.append(item)
i += 1
if limit != 0 and i == limit:
break
return tmp
def addFile(self, item, mimetype, groups=None, private=False):
# check if entry already there
if item in self.RecentFiles:
index = self.RecentFiles.index(item)
recent = self.RecentFiles[index]
else:
# delete if more then 500 files
if len(self.RecentFiles) == 500:
self.RecentFiles.pop()
# add entry
recent = RecentFile()
self.RecentFiles.append(recent)
recent.URI = item
recent.MimeType = mimetype
recent.Timestamp = int(time.time())
recent.Private = private
recent.Groups = groups
self.sort()
def deleteFile(self, item):
if item in self.RecentFiles:
self.RecentFiles.remove(item)
def sort(self):
self.RecentFiles.sort()
self.RecentFiles.reverse()
class RecentFile:
def __init__(self):
self.URI = ""
self.MimeType = ""
self.Timestamp = ""
self.Private = False
self.Groups = []
def __cmp__(self, other):
return cmp(self.Timestamp, other.Timestamp)
def __eq__(self, other):
if self.URI == str(other):
return True
else:
return False
def __str__(self):
return self.URI

View File

@ -0,0 +1 @@
__all__ = [ "BaseDirectory", "DesktopEntry", "Menu", "Exceptions", "IniFile", "IconTheme", "Locale", "Config", "Mime", "RecentFiles", "MenuEditor" ]

View File

@ -0,0 +1,116 @@
#!/usr/bin/env python
# /**************************************************************************
#
# Copyright (c) 2005-10 Simon Peter
#
# All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# **************************************************************************/
import os, sys, commands, glob
import xdg.IconTheme, xdg.DesktopEntry # apt-get install python-xdg, present on Ubuntu
from locale import gettext as _
class AppDirXdgHandler(object):
def __init__(self, appdir):
self.appdir = appdir
# print _("AppDir: %s" % [self.appdir])
newicondirs = [appdir]
for icondir in xdg.IconTheme.icondirs:
if icondir.startswith(xdg.IconTheme.basedir):
icondir = self.appdir + icondir
newicondirs.append(icondir)
xdg.IconTheme.icondirs = newicondirs + xdg.IconTheme.icondirs # search AppDir, then system
self.desktopfile = self._find_desktop_file_in_appdir()
try: self.name = self.get_nice_name_from_desktop_file(self.desktopfile)
except: self.name = "Unknown"
try: self.executable = self.get_exec_path(self.get_exec_name_from_desktop_file(self.desktopfile))
except: self.executable = None
try: self.icon = self.get_icon_path_by_icon_name(self.get_icon_name_from_desktop_file(self.desktopfile))
except: self.icon = None
def __repr__(self):
try: return("%s AppDir %s with desktop file %s, executable %s and icon %s" % (self.name, self.appdir, self.desktopfile, self.executable, self.icon))
except: return("AppDir")
def get_icon_path_by_icon_name(self, icon_name):
"""Return the path of the icon for a given icon name"""
for format in ["png", "xpm"] :
icon = xdg.IconTheme.getIconPath(icon_name, 48, None, format)
if icon != None :
return icon
return None # If we have not found an icon
def _find_all_executables_in_appdir(self):
"""Return all executable files in the AppDir, or None"""
results = []
result, executables = commands.getstatusoutput("find " + self.appdir + " -type f -perm -u+x")
executables = executables.split("\n")
if result != 0:
return None
for executable in executables:
if os.path.basename(executable) != "AppRun":
results.append(executable)
return results
def _find_desktop_file_in_appdir(self):
try:
result = glob.glob(self.appdir + '/*.desktop')[0]
return result
except:
return None
def get_icon_name_from_desktop_file(self, desktop_file):
"Returns the Icon= entry of a given desktop file"
icon_name = os.path.basename(xdg.DesktopEntry.DesktopEntry(filename=desktop_file).getIcon())
# print icon_name
return icon_name
def get_exec_name_from_desktop_file(self, desktop_file):
"Returns the Exec= entry of a given desktop file"
exec_name = os.path.basename(xdg.DesktopEntry.DesktopEntry(filename=desktop_file).getExec()).replace(" %u","").replace(" %U","").replace(" %f","").replace(" %F","")
# print exec_name
return exec_name
def get_nice_name_from_desktop_file(self, desktop_file):
"Returns the Name= entry of a given desktop file"
name = xdg.DesktopEntry.DesktopEntry(filename=desktop_file).getName()
return name
def get_exec_path(self, exec_name):
results = []
for excp in ["", "bin", "sbin", "usr/bin", "usr/sbin", "usr/games"]:
trial = os.path.join(self.appdir, excp, exec_name)
if os.path.exists(trial):
return trial
return None
if __name__ == "__main__":
if len(sys.argv) < 2:
print _("Usage: %s AppDir" % (sys.argv[0]))
exit(1)
appdir = sys.argv[1]
H = AppDirXdgHandler(appdir)
print H

Binary file not shown.

Binary file not shown.

Binary file not shown.