]>
Commit | Line | Data |
---|---|---|
1 | # -*- mode: python; tab-width: 4 -*- | |
2 | # | |
3 | # Micropolis, Unix Version. This game was released for the Unix platform | |
4 | # in or about 1990 and has been modified for inclusion in the One Laptop | |
5 | # Per Child program. Copyright (C) 1989 - 2007 Electronic Arts Inc. If | |
6 | # you need assistance with this program, you may contact: | |
7 | # http://wiki.laptop.org/go/Micropolis or email micropolis@laptop.org. | |
8 | # | |
9 | # This program is free software: you can redistribute it and/or modify | |
10 | # it under the terms of the GNU General Public License as published by | |
11 | # the Free Software Foundation, either version 3 of the License, or (at | |
12 | # your option) any later version. | |
13 | # | |
14 | # This program is distributed in the hope that it will be useful, but | |
15 | # WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | # General Public License for more details. You should have received a | |
18 | # copy of the GNU General Public License along with this program. If | |
19 | # not, see <http://www.gnu.org/licenses/>. | |
20 | # | |
21 | # ADDITIONAL TERMS per GNU GPL Section 7 | |
22 | # | |
23 | # No trademark or publicity rights are granted. This license does NOT | |
24 | # give you any right, title or interest in the trademark SimCity or any | |
25 | # other Electronic Arts trademark. You may not distribute any | |
26 | # modification of this program using the trademark SimCity or claim any | |
27 | # affliation or association with Electronic Arts Inc. or its employees. | |
28 | # | |
29 | # Any propagation or conveyance of this program must include this | |
30 | # copyright notice and these terms. | |
31 | # | |
32 | # If you convey this program (or any modifications of it) and assume | |
33 | # contractual liability for the program to recipients of it, you agree | |
34 | # to indemnify Electronic Arts for any liability that those contractual | |
35 | # assumptions impose on Electronic Arts. | |
36 | # | |
37 | # You may not misrepresent the origins of this program; modified | |
38 | # versions of the program must be marked as such and not identified as | |
39 | # the original program. | |
40 | # | |
41 | # This disclaimer supplements the one included in the General Public | |
42 | # License. TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS | |
43 | # PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY | |
44 | # OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK. THE ENTIRE RISK OF | |
45 | # SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU. ELECTRONIC ARTS | |
46 | # DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES, | |
47 | # INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY, | |
48 | # FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY | |
49 | # RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING, | |
50 | # USAGE, OR TRADE PRACTICE. ELECTRONIC ARTS DOES NOT WARRANT AGAINST | |
51 | # INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL | |
52 | # MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE | |
53 | # UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE | |
54 | # WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE | |
55 | # CORRECTED. NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR | |
56 | # ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SOME | |
57 | # JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED | |
58 | # WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A | |
59 | # CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY | |
60 | # NOT APPLY TO YOU. | |
61 | ||
62 | import gtk | |
63 | import os | |
64 | import signal | |
65 | import tempfile | |
66 | import logging | |
67 | import sys | |
68 | import time | |
69 | import subprocess | |
70 | import thread | |
71 | import fcntl | |
72 | ||
73 | from sugar.activity import activity | |
74 | from sugar.activity.activity import get_bundle_path | |
75 | from sugar import profile | |
76 | from gettext import gettext as _ | |
77 | from glob import glob | |
78 | ||
79 | try: | |
80 | import pygame.mixer | |
81 | pygame.mixer.init() | |
82 | except: pass | |
83 | ||
84 | try: | |
85 | from sugar.presence import presenceservice | |
86 | except ImportError: | |
87 | from sugar.presence import PresenceService as presenceservice | |
88 | ||
89 | ||
90 | def QuoteTCL(s): | |
91 | return s.replace('"', '\\"') | |
92 | ||
93 | ||
94 | class MicropolisActivity(activity.Activity): | |
95 | ||
96 | def __init__(self, handle): | |
97 | ||
98 | activity.Activity.__init__(self, handle) | |
99 | ||
100 | self.set_title(_('Micropolis Activity')) | |
101 | self.connect('destroy', self._destroy_cb) | |
102 | self.connect('focus-in-event', self._focus_in_cb) | |
103 | self.connect('focus-out-event', self._focus_out_cb) | |
104 | ||
105 | signal.signal(signal.SIGCHLD, self._sigchild_handler) | |
106 | ||
107 | self._bundle_path = get_bundle_path() | |
108 | ||
109 | if False: | |
110 | # FIXME: Plug Micropolis's window into a gtk socket. | |
111 | # Doesn't work yet, but it would be cool if it did. | |
112 | socket = gtk.Socket() | |
113 | try: | |
114 | self.set_canvas(socket) | |
115 | except AttributeError: | |
116 | self.add(socket) | |
117 | socket.show() | |
118 | socket.connect('plug-added', self._plug_added_cb) | |
119 | socket.connect('plug-removed', self._plug_removed_cb) | |
120 | ||
121 | win = socket.get_id() | |
122 | ||
123 | command = os.path.join( | |
124 | self._bundle_path, | |
125 | 'Micropolis') | |
126 | ||
127 | args = [ | |
128 | command, | |
129 | #'-R', str(win), # Set root window to socket window id | |
130 | '-t', # Interactive tty mode, so we can send it commands. | |
131 | ] | |
132 | ||
133 | logging.debug("CWD: " + self._bundle_path) | |
134 | logging.debug("Micropolis ARGS: " + repr(args)) | |
135 | ||
136 | self._process = subprocess.Popen( | |
137 | args, | |
138 | stdin=subprocess.PIPE, | |
139 | stdout=subprocess.PIPE, | |
140 | close_fds=True, | |
141 | cwd=self._bundle_path, | |
142 | preexec_fn=lambda: os.chdir(self._bundle_path)) | |
143 | ||
144 | logging.debug("STARTING THREAD... " + str(self._stdout_thread_function)) | |
145 | t = None | |
146 | try: | |
147 | t = thread.start_new( | |
148 | self._stdout_thread_function, | |
149 | ()) | |
150 | except Exception, e: | |
151 | logging.debug("EXCEPTION " + str(e)) | |
152 | self._stdout_thread = t | |
153 | logging.debug("STARTED THREAD. " + str(t)) | |
154 | ||
155 | uri = handle.uri or '' | |
156 | logging.debug("Micropolis SUGARSTARTUP URI " + repr(uri)) | |
157 | self.send_process( | |
158 | 'SugarStartUp "' + QuoteTCL(uri) + '"\n') | |
159 | ||
160 | nick = profile.get_nick_name() or '' | |
161 | logging.debug("Micropolis SUGARNICKNAME NICK " + repr(nick)) | |
162 | self.send_process( | |
163 | 'SugarNickName "' + QuoteTCL(nick) + '"\n') | |
164 | ||
165 | #logging.debug("started Micropolis, pid " + repr(self._pid)) | |
166 | ||
167 | ps = presenceservice.get_instance() | |
168 | ||
169 | for buddy in ps.get_buddies(): | |
170 | self._buddy_appeared_cb(ps, buddy) | |
171 | ||
172 | ps.connect("buddy-appeared", self._buddy_appeared_cb) | |
173 | ps.connect("buddy-disappeared", self._buddy_disappeared_cb) | |
174 | ||
175 | ||
176 | def _stdout_thread_function(self, *args, **keys): | |
177 | logging.debug("_stdout_thread_function BEGIN " + repr(args) + " " + repr(keys)) | |
178 | f = self._process.stdout | |
179 | fcntl.fcntl(f.fileno(), fcntl.F_SETFD, 0) | |
180 | while True: | |
181 | line = 'XXX' | |
182 | try: | |
183 | line = f.readline() | |
184 | except Exception, e: | |
185 | logging.debug("READLINE EXCEPTION " + str(e)) | |
186 | break | |
187 | logging.debug("LINE: " + repr(line)) | |
188 | line = line.strip() | |
189 | if not line: | |
190 | continue | |
191 | words = line.strip().split(' ') | |
192 | command = words[0] | |
193 | if command == 'PlaySound': | |
194 | logging.debug("PLAYSOUND " + " ".join(words[1:])) | |
195 | self.play_sound(words[1]) | |
196 | else: | |
197 | pass # logging.debug(">>> " + line) | |
198 | logging.debug("_stdout_thread_function END") | |
199 | ||
200 | ||
201 | def play_sound(self, name): | |
202 | fileName = os.path.join( | |
203 | self._bundle_path, | |
204 | 'res/sounds', | |
205 | name.lower() + '.wav') | |
206 | print "PLAY_SOUND " + fileName | |
207 | try: | |
208 | sound = pygame.mixer.Sound(fileName) | |
209 | sound.play() | |
210 | except Exception, e: | |
211 | print "Can't play sound: " + fileName + " " + str(e) | |
212 | pass | |
213 | ||
214 | ||
215 | def send_process(self, message): | |
216 | logging.debug("SEND_PROCESS " + message) | |
217 | self._process.stdin.write(message) | |
218 | ||
219 | ||
220 | def share(self): | |
221 | logging.debug("SHARE") | |
222 | Activity.share(self) | |
223 | self.send_process( | |
224 | 'SugarShare\n') | |
225 | ||
226 | ||
227 | def quit_process(self): | |
228 | logging.debug("QUIT_PROCESS") | |
229 | self.send_process( | |
230 | 'SugarQuit\n') | |
231 | time.sleep(10) | |
232 | ||
233 | ||
234 | def _plug_added_cb(self, sock): | |
235 | logging.debug("Micropolis window opened") | |
236 | return False | |
237 | ||
238 | ||
239 | def _plug_removed_cb(self, sock): | |
240 | logging.debug("Micropolis window closed") | |
241 | self.destroy() | |
242 | return False | |
243 | ||
244 | ||
245 | def _destroy_cb(self, window): | |
246 | logging.debug("Micropolis activity destroyed %r" % window) | |
247 | self.quit_process() | |
248 | ||
249 | ||
250 | def _focus_in_cb(self, window, event): | |
251 | logging.debug("Micropolis activated %r %r" % (window, event)) | |
252 | self.send_process( | |
253 | 'SugarActivate\n') | |
254 | ||
255 | ||
256 | def _focus_out_cb(self, window, event): | |
257 | logging.debug("Micropolis deactivated %r %r" % (window, event)) | |
258 | self.send_process( | |
259 | 'SugarDeactivate\n') | |
260 | ||
261 | ||
262 | def _buddy_appeared_cb(self, ps, buddy): | |
263 | ||
264 | try: | |
265 | key = buddy.props.key or '' | |
266 | nick = buddy.props.nick or '' | |
267 | color = buddy.props.color or '' | |
268 | address = buddy.props.ip4_address or '' | |
269 | except AttributeError: | |
270 | key = buddy.get_name() or '' | |
271 | nick = buddy.get_name() or '' | |
272 | color = buddy.get_color() or '' | |
273 | address = buddy.get_ip4_address() or '' | |
274 | ||
275 | logging.debug("Micropolis _BUDDY_APPEARED_CB KEY " + repr(key) + " NICK " + repr(nick) + " COLOR " + repr(color) + " ADDRESS " + repr(address)) | |
276 | ||
277 | logging.debug("Buddy appeared " + repr(buddy.props.nick)) | |
278 | ||
279 | self.send_process( | |
280 | 'SugarBuddyAdd "' + | |
281 | QuoteTCL(key) + '" "' + | |
282 | QuoteTCL(nick) + '" "' + | |
283 | QuoteTCL(color) + '" "' + | |
284 | QuoteTCL(address) + '"\n') | |
285 | ||
286 | def _buddy_disappeared_cb(self, ps, buddy): | |
287 | ||
288 | try: | |
289 | key = buddy.props.key or '' | |
290 | nick = buddy.props.nick or '' | |
291 | color = buddy.props.color or '' | |
292 | address = buddy.props.ip4_address or '' | |
293 | except AttributeError: | |
294 | key = buddy.get_name() or '' | |
295 | nick = buddy.get_name() or '' | |
296 | color = buddy.get_color() or '' | |
297 | address = buddy.get_ip4_address() or '' | |
298 | ||
299 | logging.debug("Micropolis _BUDDY_DISAPPEARED_CB KEY " + repr(key) + " NICK " + repr(nick) + " COLOR " + repr(color) + " ADDRESS " + repr(address)) | |
300 | ||
301 | logging.debug("Buddy disappeared " + repr(buddy.props.nick)) | |
302 | ||
303 | self.send_process( | |
304 | 'SugarBuddyDel "' + | |
305 | QuoteTCL(key) + '" "' + | |
306 | QuoteTCL(nick) + '" "' + | |
307 | QuoteTCL(color) + '" "' + | |
308 | QuoteTCL(address) + '"\n') | |
309 | ||
310 | def _sigchild_handler(self, signum, frame): | |
311 | logging.debug("got signal %i %r %r" % (signum, frame, self._process)) | |
312 | sys.exit(0) | |
313 |