Remove every .pyc, and add AppImageAssistant 0.9.3
parent
48729aadaf
commit
bf1bd8d7f5
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.
Binary file not shown.
Binary file not shown.
|
@ -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()
|
||||
|
|
@ -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 |
|
@ -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 |
|
@ -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()
|
|
@ -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"
|
|
@ -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))
|
|
@ -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
|
Binary file not shown.
|
@ -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.
|
@ -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
|
|
@ -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
|
|
@ -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);
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
__all__ = [ "BaseDirectory", "DesktopEntry", "Menu", "Exceptions", "IniFile", "IconTheme", "Locale", "Config", "Mime", "RecentFiles", "MenuEditor" ]
|
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue