3 # /**************************************************************************
5 # Copyright (c) 2005-13 Simon Peter
9 # Permission is hereby granted, free of charge, to any person obtaining a copy
10 # of this software and associated documentation files (the "Software"), to deal
11 # in the Software without restriction, including without limitation the rights
12 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 # copies of the Software, and to permit persons to whom the Software is
14 # furnished to do so, subject to the following conditions:
16 # The above copyright notice and this permission notice shall be included in
17 # all copies or substantial portions of the Software.
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 # ******************************************************
33 # Find out why it freezes on Fedora 12
37 from locale
import gettext
as _
41 from subprocess
import *
50 if len(sys
.argv
) == 3:
51 os
.system(os
.path
.dirname(__file__
) + "/package %s %s" % (sys
.argv
[1], sys
.argv
[2]))
55 import xdgappdir
# bundled with this app
56 import dialogs
# part of kiwi; bundled with this app
58 application_icon
= os
.path
.join(os
.path
.dirname(sys
.argv
[0]), "AppImageAssistant.png")
60 def error(string
, fatal
=True):
63 buttontype
= gtk
.BUTTONS_CANCEL
65 buttontype
= gtk
.BUTTONS_OK
66 message
= gtk
.MessageDialog(None, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_ERROR
, buttontype
, string
)
73 returns 1 if yes was pressed,
75 -1 if dialog was cancled
79 hbox
= gtk
.GtkHButtonBox()
80 def delete_event(widget
, event
, d
):
83 d
.connect("delete_event", delete_event
, d
)
85 def callback(widget
, data
):
89 d
.callback_return
=data
91 yes
= gtk
.GtkButton(yes_text
)
92 yes
.connect("clicked", callback
, (d
, 1))
95 no
= gtk
.GtkButton(no_text
)
96 no
.connect("clicked", callback
, (d
, 0))
101 d
.callback_return
=None
102 while d
.callback_return
==None:
103 gtk
.mainiteration(gtk
.TRUE
) # block until event occurs
104 return d
.callback_return
108 t
= threading
.Thread(target
=f
, args
=args
)
111 wrapper
.__name
__ = f
.__name
__
112 wrapper
.__dict
__ = f
.__dict
__
113 wrapper
.__doc
__ = f
.__doc
__
116 class Assistant(gtk
.Assistant
):
118 gtk
.Assistant
.__init
__(self
)
119 self
.connect('close', gtk
.main_quit
)
120 self
.connect('cancel',gtk
.main_quit
)
121 #self.connect('prepare', self.callback_prepare)
122 self
.set_icon_from_file(application_icon
)
123 self
.set_size_request(640, 480)
124 self
.init_intro_page()
128 def text_page(self
, header
, text
):
129 label
= gtk
.Label(text
)
131 label
.set_line_wrap(True)
132 self
.append_page(label
)
133 self
.set_page_title(label
, header
)
134 self
.set_page_complete(label
, True)
135 self
.set_page_header_image(label
, gtk
.gdk
.pixbuf_new_from_file(application_icon
))
137 def chooser_page(self
, header
):
138 chooser
= gtk
.FileChooserWidget(gtk
.FILE_CHOOSER_ACTION_SELECT_FOLDER
)
139 chooser
.connect("selection-changed", self
.check_if_appdir_callback
)
140 chooser
.connect("key-release-event", self
.check_if_appdir_callback
)
141 if len(sys
.argv
) > 1:
142 chooser
.set_current_folder(sys
.argv
[1])
144 chooser
.set_current_folder(os
.environ
.get('HOME'))
146 self
.append_page(chooser
)
147 self
.set_page_title(chooser
, header
)
148 self
.set_page_complete(chooser
, False)
149 self
.set_page_header_image(chooser
, gtk
.gdk
.pixbuf_new_from_file(application_icon
))
151 def runner_page(self
, callable):
153 vbox
.set_name("MAIN_RUNNER_PAGE")
154 label
= gtk
.Label(_("Running..."))
155 label
.set_line_wrap(True)
156 vbox
.pack_start(label
, False, False, 0)
157 runbox
= RunBox(self
)
158 runbox
.connect('realize', callable)
159 vbox
.pack_start(runbox
, True, True, 0)
160 self
.append_page(vbox
)
161 self
.set_page_title(vbox
, "Running...")
162 self
.set_page_header_image(vbox
, gtk
.gdk
.pixbuf_new_from_file(application_icon
))
164 self
.set_page_complete(vbox
, True) ##############################
165 self
.set_page_type(vbox
, gtk
.ASSISTANT_PAGE_PROGRESS
)
167 # UNTIL HERE IT IS GENERIC ====================================================
169 def result_page(self
):
172 vbox
.pack_start(icon
, False, False, 0)
174 scrolled
= gtk
.ScrolledWindow()
175 scrolled
.add_with_viewport(vbox
)
177 self
.append_page(scrolled
)
178 self
.set_page_header_image(vbox
, gtk
.gdk
.pixbuf_new_from_file(application_icon
))
180 filetype
= Popen(["file", "-k", "-r", self
.targetname
], stdout
=PIPE
).communicate()[0]
182 if "ISO 9660" in filetype
and "LSB executable" in filetype
:
183 icon
.set_from_file(os
.path
.join(os
.path
.dirname(sys
.argv
[0]), "Gnome-emblem-default.png"))
184 self
.set_page_title(vbox
, "Done")
185 self
.set_page_type(vbox
, gtk
.ASSISTANT_PAGE_SUMMARY
)
186 basesystems
= glob
.glob('/System/iso/*.iso')
187 basesystems
.append("/cdrom/casper/filesystem.squashfs")
188 for basesystem
in basesystems
:
190 button
= gtk
.Button(_("Run in %s") % (basesystem
))
191 button
.connect('clicked', self
.testrun
, basesystem
, self
.targetname
)
192 vbox
.pack_start(button
)
195 icon
.set_from_file(os
.path
.join(os
.path
.dirname(sys
.argv
[0]), "Gnome-dialog-warning.png"))
196 self
.set_page_title(icon
, "An error has occured")
198 def testrun(self
, sender
, basesystem
, appimage
):
199 # Need an involved workaround because running as root means that we cannot access files inside the AppImage due to permissions
200 shutil
.copyfile(os
.path
.join(os
.path
.dirname(sys
.argv
[0]), "testappimage"), "/tmp/testappimage")
201 shutil
.copyfile(os
.path
.join(os
.path
.dirname(sys
.argv
[0]), "unionfs-fuse"), "/tmp/unionfs-fuse")
202 os
.system("sudo chmod 755 /tmp/testappimage /tmp/unionfs-fuse")
203 os
.system("sudo xterm -hold -e /tmp/testappimage '" + basesystem
+ "' '" + appimage
+ "'")
205 def check_if_appdir_callback(self
, widget
, dummy
=False):
206 print _("Checking whether %s is an AppDir" % (widget
.get_filename()))
207 self
.set_page_complete(widget
, True)
208 candidate
= widget
.get_filename()
209 if not os
.path
.isfile(os
.path
.join(candidate
, "AppRun")):
210 self
.set_page_complete(widget
, False)
213 self
.H
= xdgappdir
.AppDirXdgHandler(candidate
)
215 if self
.H
.desktopfile
== None:
216 self
.set_page_complete(widget
, False)
217 error(_("Can't find this AppDir's desktop file"), False)
219 if self
.H
.executable
== None:
220 self
.set_page_complete(widget
, False)
221 error(_("Can't find the executable in this AppDir's desktop file"), False)
223 if self
.H
.icon
== None:
224 self
.set_page_complete(widget
, False)
225 error(_("Can't find the icon in this AppDir's desktop file"), False)
228 self
.appdir
= candidate
230 self
.targetname
= os
.path
.join(os
.path
.dirname(self
.appdir
), self
.H
.name
)
232 self
.targetname
= os
.path
.join(os
.path
.dirname(self
.appdir
), os
.path
.basename(self
.H
.executable
))
234 if os
.path
.exists(self
.targetname
):
235 self
.set_page_complete(widget
, False)
236 resp
= dialogs
.yesno(_("%s already exists, do you want to delete it?") % (self
.targetname
))
237 if resp
== gtk
.RESPONSE_YES
:
239 os
.unlink(self
.targetname
)
240 self
.set_page_complete(widget
, True)
241 self
.set_current_page(self
.get_current_page() + 1) # go to the next page
243 error(_("%s already exists, delete it first if you want to create a new one") % (self
.targetname
), False)
246 def init_intro_page(self
):
247 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.")
248 self
.chooser_page("Please select the AppDir")
249 self
.runner_page(self
.run1_func
)
251 @threaded # For this to work, the gtk.gdk.threads_init() function must be called before the gtk.main() function
252 def run1_func(self
, widget
):
253 command
= ["python", os
.path
.join(os
.path
.dirname(sys
.argv
[0]), "package"), self
.appdir
, self
.targetname
]
254 print "Running command:"
256 gtk
.gdk
.threads_enter() # this must be called in a function that is decorated with @threaded
257 widget
.run_command(command
) # this is the long-running command
258 gtk
.gdk
.threads_leave() # this must be called in a function that is decorated with @threaded
260 class RunBox(vte
.Terminal
):
261 def __init__(self
, assistant
):
262 vte
.Terminal
.__init
__(self
)
263 self
.connect('child-exited', self
.run_command_done_callback
)
264 self
.assistant
= assistant
# the assistant is passed in here so that we can e.g., disable forward buttons
267 def run_command(self
, command_list
):
268 self
.assistant
.set_page_complete(self
.assistant
.get_nth_page(self
.assistant
.get_current_page()), False)
269 self
.thread_running
= True
270 command
= command_list
271 pid
= self
.fork_command(command
=command
[0], argv
=command
, directory
=os
.getcwd())
272 while self
.thread_running
:
275 def run_command_done_callback(self
, terminal
):
277 self
.assistant
.set_page_complete(self
.assistant
.get_nth_page(self
.assistant
.get_current_page()), True) # enable the next page button
278 self
.assistant
.result_page() # only now initialize the results page because it needs to check whether we succeeded
279 self
.assistant
.set_current_page(self
.assistant
.get_current_page() + 1) # go to the next page
280 self
.thread_running
= False
283 if __name__
=="__main__":
286 gtk
.gdk
.threads_init() # The gtk.gdk.threads_init() function must be called before the gtk.main() function