Switched to using AppImageAssistant binary
|
@ -1,277 +0,0 @@
|
|||
#!/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.
|
||||
#
|
||||
# ******************************************************
|
||||
|
||||
__version__="0.9.2"
|
||||
|
||||
#
|
||||
# TODO:
|
||||
# Find out why it freezes on Fedora 12
|
||||
#
|
||||
|
||||
import os, sys, gtk, vte
|
||||
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
|
||||
|
||||
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)
|
||||
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()
|
||||
self.append_page(vbox)
|
||||
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('/cdrom/*.squashfs')
|
||||
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()
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
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 $@
|
|
@ -1,4 +0,0 @@
|
|||
#!/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"
|
|
@ -1,136 +0,0 @@
|
|||
#!/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.
|
||||
#
|
||||
# **************************************************************************/
|
||||
|
||||
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"
|
||||
|
||||
else:
|
||||
extracted_icon = "/tmp/icon"
|
||||
try:
|
||||
os.unlink(extracted_icon)
|
||||
except:
|
||||
pass
|
||||
result, output = commands.getstatusoutput("elficon -r '" + destinationfile + "' icon " + extracted_icon)
|
||||
if result != 0:
|
||||
print "elficon error: Could not read icon from elf"
|
||||
exit(1)
|
||||
iconfile = extracted_icon
|
||||
|
||||
print("Embedding icon into runtime...")
|
||||
|
||||
elf = os.path.realpath(os.path.dirname(__file__)) + "/runtime"
|
||||
result, output = commands.getstatusoutput("cp " + elf + " /tmp/")
|
||||
result, output = commands.getstatusoutput("elficon -s /tmp/runtime 123")
|
||||
print output
|
||||
if result != 0:
|
||||
print "elficon error: Could not write ID into elf"
|
||||
exit(1)
|
||||
result, output = commands.getstatusoutput("elficon -a /tmp/runtime icon '" + iconfile + "'")
|
||||
if result != 0:
|
||||
print "elficon error: Could not embed icon into elf"
|
||||
exit(1)
|
||||
|
||||
if int(os.stat("/tmp/runtime").st_size) > 32*1024: # 32k
|
||||
print (_("Runtime does not fit into ISO header; probably the icon is too large"))
|
||||
exit(1)
|
||||
|
||||
print("Embedding runtime into the header of %s..." % (destinationfile))
|
||||
|
||||
s = file("/tmp/runtime", 'r')
|
||||
f = file(destinationfile, 'r+')
|
||||
f.write(s.read())
|
||||
f.close()
|
||||
s.close()
|
||||
result, output = commands.getstatusoutput("rm /tmp/runtime")
|
||||
|
||||
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))
|
|
@ -1,9 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Name=AppImageAssistant
|
||||
Exec=AppImageAssistant
|
||||
Icon=AppImageAssistant
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Development;
|
||||
Comment=Turn AppDir into AppImage, part of AppImageKit
|
||||
StartupNotify=true
|
Before Width: | Height: | Size: 2.6 KiB |
|
@ -1,11 +0,0 @@
|
|||
#!/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 $@
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 32 KiB |
|
@ -1,437 +0,0 @@
|
|||
#
|
||||
# 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()
|
|
@ -1,4 +0,0 @@
|
|||
#!/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"
|
|
@ -1,37 +0,0 @@
|
|||
#!/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
|
|
@ -1,120 +0,0 @@
|
|||
#!/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 $?
|
|
@ -1,97 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,39 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,383 +0,0 @@
|
|||
"""
|
||||
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);
|
|
@ -1,51 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,391 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,402 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,79 +0,0 @@
|
|||
"""
|
||||
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()
|
|
@ -1,511 +0,0 @@
|
|||
""" 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)
|
|
@ -1,474 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,159 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1 +0,0 @@
|
|||
__all__ = [ "BaseDirectory", "DesktopEntry", "Menu", "Exceptions", "IniFile", "IconTheme", "Locale", "Config", "Mime", "RecentFiles", "MenuEditor" ]
|
|
@ -1,116 +0,0 @@
|
|||
#!/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
|
||||
|
|
@ -1,281 +0,0 @@
|
|||
#!/usr/bin/env python2
|
||||
|
||||
# /**************************************************************************
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ******************************************************
|
||||
|
||||
__version__="0.9.2"
|
||||
|
||||
#
|
||||
# TODO:
|
||||
# Find out why it freezes on Fedora 12
|
||||
#
|
||||
|
||||
import os, sys, gtk, vte
|
||||
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
|
||||
|
||||
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(os.environ.get('HOME'))
|
||||
else:
|
||||
chooser.set_current_folder(sys.argv[1])
|
||||
#self.check_if_appdir_callback(chooser)
|
||||
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()
|
||||
self.append_page(vbox)
|
||||
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('/cdrom/*.squashfs')
|
||||
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()
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Name=AppImageAssistant
|
||||
Exec=AppImageAssistant
|
||||
Icon=AppImageAssistant
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Development;
|
||||
Comment=Turn AppDir into AppImage, part of AppImageKit
|
||||
StartupNotify=true
|
Before Width: | Height: | Size: 2.6 KiB |
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
cd $(dirname "${0}")
|
||||
EXEC=$(grep -m 1 -r Exec= ./*.desktop | cut -d "=" -f 2 | cut -d % -f 1)
|
||||
LD_LIBRARY_PATH="./:${LD_LIBRARY_PATH}" PATH="./:${PATH}" exec $EXEC $@
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 32 KiB |
|
@ -1,437 +0,0 @@
|
|||
#
|
||||
# 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()
|
|
@ -1,4 +0,0 @@
|
|||
#!/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"
|
|
@ -1,137 +0,0 @@
|
|||
#!/usr/bin/env python2
|
||||
|
||||
# /**************************************************************************
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# **************************************************************************/
|
||||
|
||||
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"
|
||||
|
||||
else:
|
||||
extracted_icon = "/tmp/icon"
|
||||
try:
|
||||
os.unlink(extracted_icon)
|
||||
except:
|
||||
pass
|
||||
result, output = commands.getstatusoutput("elficon -r '" + destinationfile + "' icon " + extracted_icon)
|
||||
if result != 0:
|
||||
print "elficon error: Could not read icon from elf"
|
||||
exit(1)
|
||||
iconfile = extracted_icon
|
||||
|
||||
print("Embedding icon into runtime...")
|
||||
|
||||
elf = os.path.realpath(os.path.dirname(__file__)) + "/runtime"
|
||||
result, output = commands.getstatusoutput("cp \"" + elf + "\" /tmp/")
|
||||
result, output = commands.getstatusoutput("elficon -s /tmp/runtime 123")
|
||||
print output
|
||||
if result != 0:
|
||||
print "elficon error: Could not write ID into elf"
|
||||
exit(1)
|
||||
result, output = commands.getstatusoutput("elficon -a /tmp/runtime icon '" + iconfile + "'")
|
||||
if result != 0:
|
||||
print "elficon error: Could not embed icon into elf"
|
||||
exit(1)
|
||||
|
||||
if int(os.stat("/tmp/runtime").st_size) > 32*1024: # 32k
|
||||
print (_("Runtime does not fit into ISO header; probably the icon is too large"))
|
||||
print (_("Size is %s and should be less than %s")) % (os.stat("/tmp/runtime").st_size, 32*1024)
|
||||
#exit(1)
|
||||
|
||||
print("Embedding runtime into the header of %s..." % (destinationfile))
|
||||
|
||||
s = file("/tmp/runtime", 'r')
|
||||
f = file(destinationfile, 'r+')
|
||||
f.write(s.read())
|
||||
f.close()
|
||||
s.close()
|
||||
result, output = commands.getstatusoutput("rm /tmp/runtime")
|
||||
|
||||
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))
|
|
@ -1,37 +0,0 @@
|
|||
#!/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
|
|
@ -1,75 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
# set -x
|
||||
|
||||
HERE=$(dirname $(readlink -f "${0}"))
|
||||
export PATH=$HERE:$PATH
|
||||
|
||||
if [ "$1x" == "x" ] ; then
|
||||
echo "Please specify a squashfs base system to run the AppImage on"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$2x" == "x" ] ; then
|
||||
echo "Please specify an AppImage to be run"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p /tmp/unionfs/root
|
||||
mkdir -p /tmp/unionfs/rw
|
||||
mkdir -p /tmp/union
|
||||
|
||||
trap atexit EXIT
|
||||
|
||||
atexit()
|
||||
{
|
||||
echo ""
|
||||
echo "===================================================="
|
||||
echo ""
|
||||
set +e
|
||||
umount -l /tmp/union/var/lib/dbus
|
||||
umount -l /tmp/union/etc/resolv.conf
|
||||
umount -l /tmp/union/proc
|
||||
umount -l /tmp/union/boot
|
||||
umount -l /tmp/union
|
||||
umount -l /tmp/unionfs/root
|
||||
rm -r /tmp/unionfs/root
|
||||
rm -r /tmp/unionfs/rw
|
||||
rm -r /tmp/unionfs
|
||||
rm -r /tmp/union
|
||||
}
|
||||
|
||||
mount "$1" /tmp/unionfs/root -o loop
|
||||
|
||||
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
|
||||
|
||||
if [ "x$MNT" == "x" ] ; then
|
||||
echo "Could not find free mountpoint"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mount "$2" /tmp/union/boot -o loop
|
||||
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
|
||||
$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
|
||||
exit $?
|
|
@ -1 +0,0 @@
|
|||
libr.so.0.0.0
|
|
@ -1,97 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,39 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,383 +0,0 @@
|
|||
"""
|
||||
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);
|
|
@ -1,51 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,391 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,402 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,79 +0,0 @@
|
|||
"""
|
||||
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()
|
|
@ -1,511 +0,0 @@
|
|||
""" 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)
|
|
@ -1,474 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1,159 +0,0 @@
|
|||
"""
|
||||
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
|
|
@ -1 +0,0 @@
|
|||
__all__ = [ "BaseDirectory", "DesktopEntry", "Menu", "Exceptions", "IniFile", "IconTheme", "Locale", "Config", "Mime", "RecentFiles", "MenuEditor" ]
|
|
@ -1,116 +0,0 @@
|
|||
#!/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
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2
|
||||
|
||||
# /**************************************************************************
|
||||
#
|
||||
|
@ -46,10 +46,51 @@ import xdgappdir
|
|||
import commands
|
||||
import threading
|
||||
import glob
|
||||
import colors
|
||||
|
||||
def usage():
|
||||
print "Usage:"
|
||||
print " ", colors.BOLD, "--pack, --package", colors.END, "<input AppDir>", "<output AppImage>"
|
||||
print " "*6, "Create an AppImage from an AppDir"
|
||||
print " ", colors.BOLD, "--unpack, --extract", colors.END, "<input AppImage>", "<output AppDir>", "[<path inside AppImage>]"
|
||||
print " "*6, "Extract an AppImage to an AppDir"
|
||||
print " ", colors.BOLD, "-h, --help", colors.END
|
||||
print " "*6, "Show this help message"
|
||||
print " ", colors.BOLD, "<no arguments>", colors.END
|
||||
print " "*6, "Start AppImage creation wizard"
|
||||
|
||||
for arg in sys.argv:
|
||||
if arg in ["--pack", "--package"]:
|
||||
if len(sys.argv) < 4:
|
||||
usage()
|
||||
elif not os.path.exists(sys.argv[2]):
|
||||
print sys.argv[2], "doesn't exist"
|
||||
elif not os.path.isdir(sys.argv[2]):
|
||||
print sys.argv[2], "is not a directory"
|
||||
elif os.path.exists(sys.argv[3]):
|
||||
print sys.argv[3], "already exists"
|
||||
else:
|
||||
os.system(os.path.dirname(__file__) + "/package '%s' '%s'" % (sys.argv[2], sys.argv[3]))
|
||||
exit(0)
|
||||
elif arg in ["--unpack", "--extract"]:
|
||||
if len(sys.argv) < 4:
|
||||
usage()
|
||||
elif not os.path.exists(sys.argv[2]):
|
||||
print sys.argv[2], "doesn't exist"
|
||||
elif not os.path.isfile(sys.argv[2]):
|
||||
print sys.argv[2], "is not a file"
|
||||
elif os.path.exists(sys.argv[3]):
|
||||
print sys.argv[3], "already exists"
|
||||
else:
|
||||
origin = "/"
|
||||
if len(sys.argv) >= 5:
|
||||
origin = sys.argv[4]
|
||||
os.system(os.path.dirname(__file__) + "/extract '%s' '%s' '%s'" % (sys.argv[2], sys.argv[3], origin))
|
||||
exit(0)
|
||||
elif arg in ["-h", "--help"]:
|
||||
usage()
|
||||
exit(0)
|
||||
|
||||
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
|
||||
|
@ -179,7 +220,7 @@ class Assistant(gtk.Assistant):
|
|||
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:
|
||||
if "ISO 9660" in filetype and "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)
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,3 @@
|
|||
#!/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 "$@"
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
@ -0,0 +1,10 @@
|
|||
PURPLE = '\033[95m'
|
||||
CYAN = '\033[96m'
|
||||
DARKCYAN = '\033[36m'
|
||||
BLUE = '\033[94m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
RED = '\033[91m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
END = '\033[0m'
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
cd $(dirname "${0}")
|
||||
NAME=$(basename "${1}")
|
||||
DESTINATION="${2:-$HOME/Desktop/${NAME}.AppDir}"
|
||||
ORIGIN="${3:-/}"
|
||||
LD_LIBRARY_PATH="./usr/lib/:${LD_LIBRARY_PATH}" PATH="./usr/bin:${PATH}" xorriso -indev "${1}" -osirrox on -extract "$ORIGIN" "$DESTINATION"
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2
|
||||
|
||||
# /**************************************************************************
|
||||
#
|
||||
|
@ -30,7 +30,6 @@ from __future__ import division
|
|||
import os, sys
|
||||
import subprocess
|
||||
import xdgappdir
|
||||
import commands
|
||||
from locale import gettext as _
|
||||
|
||||
|
||||
|
@ -43,7 +42,7 @@ 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"
|
||||
print("Assuming I should inject a new runtime")
|
||||
destinationfile = os.path.realpath(sys.argv[1])
|
||||
should_compress = False
|
||||
elif len(sys.argv) < 3:
|
||||
|
@ -67,39 +66,38 @@ 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"
|
||||
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
|
||||
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",
|
||||
command = ["xorriso", "-dev",
|
||||
destinationfile, "-padding", "0", "-map",
|
||||
sourcedir, "/", "--", "-map", iconfile, "/.DirIcon",
|
||||
sourcedir, "/", "--", "-map", iconfile, "/.DirIcon",
|
||||
"-zisofs", "level=9:block_size=128k", "-chown_r", "0",
|
||||
"/", "--", "set_filter_r", "--zisofs", "/" ]
|
||||
|
||||
|
||||
subprocess.Popen(command).communicate()
|
||||
|
||||
print "ok"
|
||||
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())
|
||||
s = open(elf, 'rb')
|
||||
f = open(destinationfile, 'rb+')
|
||||
f.write(bytes(s.read()))
|
||||
f.close()
|
||||
s.close()
|
||||
print "ok"
|
||||
print("ok")
|
||||
|
||||
print("Making %s executable..." % (destinationfile))
|
||||
|
||||
os.chmod(destinationfile, 0755)
|
||||
os.chmod(destinationfile, 0o755)
|
||||
|
||||
print "ok"
|
||||
print("ok")
|
||||
|
||||
filesize = int(os.stat(destinationfile).st_size)
|
||||
print (_("Size: %f MB") % (filesize/1024/1024))
|