618 Commits

Author SHA1 Message Date
Aaron Culliney
2739a6b084 Bumped versionCode for next RC build 2020-01-26 09:13:16 -08:00
Aaron Culliney
ead2ac32f5 Fix testdisk on Android 2020-01-11 15:13:20 -10:00
Aaron Culliney
76f98557b7 Fix/improve test broken by 82813b3ac 2020-01-10 16:47:24 -10:00
Aaron Culliney
294c6ca790 Update Android Studio thingz 2019-12-02 10:54:34 -08:00
Aaron Culliney
21fff2cc15 Build ABI specific APKs 2019-12-01 15:18:00 -08:00
Aaron Culliney
2bda54d342 Various project changes 2019-12-01 15:17:51 -08:00
Aaron Culliney
a5a4a50983 HACK: Experimental fix for speaker-getting-wedged issue
- Repro: GDB breakpoint while cpu_thread() is nanosleeping() seems to produce maximum cycles_speaker_feedback, and it never clears up
2019-12-01 15:12:10 -08:00
Aaron Culliney
d63ba9538d Refactor email-logs and crash handler codepaths
- Use a new activity and change to Intent.ACTION_SEND handler
    - Fixes an issue where email data was previously lost on a re-orientation event
2019-12-01 11:38:30 -08:00
Aaron Culliney
2728d44f8c Fix bug in system disk chooser where orientation event prevented actual selection 2019-12-01 11:25:24 -08:00
Aaron Culliney
ad0a8400ef Eliminate a warning 2019-12-01 11:25:05 -08:00
Aaron Culliney
4616a41326 Correctly symbolicate for arm64-v8a and x86_64 2019-11-30 09:28:26 -08:00
Aaron Culliney
a470329143 Bumped versionCode for next RC build 2019-11-30 09:19:06 -08:00
Aaron Culliney
ab482fc3dc Fix invisible menu checkboxes on older Droids 2019-11-30 08:35:20 -08:00
Aaron Culliney
2ef5abba2c Add an email logs menu item that sends app data 2019-11-30 07:59:14 -08:00
Aaron Culliney
68e6fb768f Avoid UI race when copying assets to storage 2019-11-29 14:03:09 -08:00
Aaron Culliney
296bd12029 Fix release notes and allow internal/external storage disk selection to work on Android 10 2019-11-29 14:02:10 -08:00
Aaron Culliney
f5899e74d6 Zip up crash and log files on Droid to email to developer 2019-11-29 12:41:34 -08:00
Aaron Culliney
310825e2cc Route Android logging to native side and timestamp logs 2019-11-29 08:53:16 -08:00
Aaron Culliney
46b74f65fa Update release notes 2019-11-10 16:22:09 -08:00
Aaron Culliney
397b43b3e7 Improve joystick button reset procedure 2019-11-10 16:10:08 -08:00
Aaron Culliney
5aa691815b Support open/closed apple tap events from touch keyboard 2019-11-10 15:30:58 -08:00
Aaron Culliney
c0fd258c40 Shuffle some Android settings around 2019-11-10 12:34:09 -08:00
Aaron Culliney
acade0076b Fix Android Studio lint warning 2019-11-10 08:31:33 -08:00
Aaron Culliney
cff22f6b34 Correctly show chosen file name in disks menu 2019-11-10 08:30:00 -08:00
Aaron Culliney
bf4f76a142 Use origin value for touch joysticks if pythagorean result is 0 2019-11-10 06:58:27 -08:00
Aaron Culliney
f654fb0825 Improve calibration routine for touch keypad joystick 2019-11-10 06:58:16 -08:00
Aaron Culliney
9a1d305975 Remove more unnecessary negative buttons 2019-11-09 16:10:46 -08:00
Aaron Culliney
e224912f8b Correctly set cpu65_vmem_X function table sizes :P 2019-10-31 17:37:26 -07:00
Aaron Culliney
1e986d73ef Improve button positioning
- Keeps similar consistent locations as prior menus
    - Remove cancel no-op buttons
2019-10-31 17:36:25 -07:00
Aaron Culliney
dfc09e87bb Correctly set the filename from chooser activity 2019-10-31 17:34:28 -07:00
Aaron Culliney
57d1e95cce Improve background color and layout 2019-10-31 17:33:41 -07:00
Aaron Culliney
d6e21fd967 Fix a crash in disk selection 2019-10-31 17:33:14 -07:00
Aaron Culliney
37df4796c9 Updated Android build tools and first RC 2019-10-28 07:27:41 -07:00
Aaron Culliney
76c9daeb4c New style for Android app 2019-10-28 07:25:57 -07:00
Aaron Culliney
e70051d6a4 Migrate to AndroidX 2019-10-27 17:40:01 -07:00
Aaron Culliney
7ac274261a Migrate to AppCompat 2019-10-27 17:36:36 -07:00
Aaron Culliney
cf01bb7985 Ensure proper setting of Android 64bit flags 2019-10-27 16:46:48 -07:00
Aaron Culliney
8f2ab2f072 Compress cpu65_vmem_* function tables to save space 2019-10-27 16:26:35 -07:00
Aaron Culliney
00984b7ab9 Compile on Android with more warnings 2019-10-27 13:10:29 -07:00
Aaron Culliney
1491d3f88d Streamline 65c02 cpu on ARM 2019-10-21 07:35:54 -07:00
Aaron Culliney
2615351d46 Optimize and de-macroize BIT computation on ARM 2019-10-20 18:44:06 -07:00
Aaron Culliney
f608de450e Allow super twitchy touch joystick 2019-10-20 18:40:59 -07:00
Aaron Culliney
c59b18d176 Inscrutible changes insisted upon by Android Studio 2019-10-06 17:05:51 -07:00
Aaron Culliney
1c022c9587 Get our copy of breakpad, such as it is, building against NDK R15C
- Likely we should "soon" upgrade breakpad/crashpad and the NDK to a mutually agreed-upon version
2019-10-06 17:05:16 -07:00
Aaron Culliney
43d99ec9f4 First cut at 65c02 implemented on aarch64 2019-10-06 17:05:08 -07:00
Aaron Culliney
4e27c1a322 Rename internal _debugger_go() to avoid name collision with main debugger_go() 2019-10-06 16:28:44 -07:00
Aaron Culliney
82813b3ac8 Use correct initial pointers 2019-10-06 16:25:59 -07:00
Aaron Culliney
ad95368a8c Can debug again on Pixely devices ... 2019-10-06 16:22:38 -07:00
Aaron Culliney
135ccb6b2d Fix build break in testprefs 2019-10-06 16:18:39 -07:00
Aaron Culliney
7591715357 Fixes to get CPU tracing building again 2019-10-06 16:18:30 -07:00
Aaron Culliney
317f83a44b Inscrutable changes insisted upon by IB 2019-08-09 12:10:44 +05:30
Aaron Culliney
3bad383cbe Fix iOS build 2019-08-09 11:40:15 +05:30
Aaron Culliney
96e75f50ff Avoid doing stderr logging, period 2019-06-28 21:10:07 -07:00
Aaron Culliney
17147ce662 LOG strerror(errno) for functions that may fail 2019-06-28 20:17:37 -07:00
Aaron Culliney
348eeb1f09 Avoid strerror(errno) spam in logs 2019-06-28 20:06:34 -07:00
Aaron Culliney
e8e3110d18 Remove an aggressive assert for overtaxed audio systems 2019-06-28 19:21:07 -07:00
Aaron Culliney
412fb06011 Slowpath debugging interface 2019-06-28 18:31:08 -07:00
Aaron Culliney
fe714af37c Kill some unnecessary uses of volatile 2019-06-28 18:30:45 -07:00
Aaron Culliney
45cc3332fb Fixes for Linux build 2019-06-27 16:22:05 -07:00
Aaron Culliney
7eacfb4564 Provide more visual feedback when calibrating joystick variants 2019-06-09 16:58:53 -07:00
Aaron Culliney
e643b1c855 Remove misguided justTapConfigured code path 2019-06-09 14:04:14 -07:00
Aaron Culliney
4515e5d7b7 Support touch joystick button fire when swiping left/right 2019-06-09 13:02:27 -07:00
Aaron Culliney
713d9a99e2 More complete feature changes 2019-06-09 11:51:15 -07:00
Aaron Culliney
fcfd32b843 Allow Android device sensor portrait mode 2019-06-09 11:14:28 -07:00
Aaron Culliney
c98777c6ae Correctly handle shifted keys in JSON touch keypad joystick and simplify associated JSON preferences 2019-06-09 10:53:31 -07:00
Aaron Culliney
2878c5adde Partial Revert "Fastpath no-logging, don't do normal logging in release but provide a means to RELEASE_LOG()"
- Logging to file may still be useful in release, and we already don't do logging to logcat/stdout
    - TODO: verify no hotpath logging in release ...
2019-06-02 13:12:10 -07:00
Aaron Culliney
788c6cb172 Refactor touch joystick variants
- Use end-of-video-frame callbacks for more conformant delay timing
    - Allow full octant for button side in touch keypad joystick
    - Improved response upon unambiguous touch event (e.g., octant change) to immediately press key
2019-06-02 13:08:16 -07:00
Aaron Culliney
a25d68a1d2 Remove old preference migration codepath 2019-06-02 08:25:08 -07:00
Aaron Culliney
44e7bda8ac Kill some build warnings 2019-06-01 17:47:35 -07:00
Aaron Culliney
5efd1099bc Build x86_64 for Android 2019-06-01 17:17:53 -07:00
Aaron Culliney
3579423890 Upgraded Android Studio 2019-06-01 17:17:20 -07:00
Aaron Culliney
20852bf737 Lite refactor keys API 2019-06-01 07:27:54 -07:00
Aaron Culliney
1a32756bd4 Fastpath no-logging, don't do normal logging in release but provide a means to RELEASE_LOG() 2019-06-01 07:24:30 -07:00
Aaron Culliney
5a8aa065a9 Refactor/simplify spinlocking 2019-06-01 07:13:59 -07:00
Aaron Culliney
ef3944a4dd Provide callback API for end-of-video-frame 2019-06-01 07:01:42 -07:00
Aaron Culliney
9e8a5e2134 Rename some debugger functions and mark as TESTING 2019-03-24 15:50:16 -07:00
Aaron Culliney
1c61071a11 Lite refactor debugger interface 2019-03-24 15:19:58 -07:00
Aaron Culliney
3ca0d9b618 Add some new keyboard variants for the given RPGs 2019-03-10 12:18:27 -07:00
Aaron Culliney
9b110224a0 Lite refactor/rename keys internal API 2019-03-10 12:17:06 -07:00
Aaron Culliney
faf7d707ed Unbreak INTERFACE_CLASSIC rendering 2019-03-09 06:56:49 -08:00
Aaron Culliney
5bd8e25739 Improved two-thumb support for touch keyboard 2019-03-02 17:23:50 -08:00
Aaron Culliney
75be89d1ab Promote alt keyboard selection to a higher menu order 2019-03-02 16:23:16 -08:00
Aaron Culliney
f038ef0346 Unify and brace thread creation with TEMP_FAILURE_RETRY() 2019-02-24 09:24:12 -08:00
Aaron Culliney
d90e12b5dc Rename touch joystick variants for code clarity 2019-02-17 08:12:23 -08:00
Aaron Culliney
c22ad4c1e9 Remove ancient text files 2018-12-08 07:57:09 -08:00
Aaron Culliney
1bf328795a Silence some Android build warnings 2018-12-02 09:10:11 -08:00
Aaron Culliney
aa74763d28 Bump Android version to 2.0.1 2018-11-25 19:01:45 -08:00
Aaron Culliney
36c83c62e3 Make sure data_dir and logging are initialized for all platforms 2018-11-25 18:57:26 -08:00
Aaron Culliney
27a848f61b Android 2.0.0 RC2 2018-11-25 16:26:58 -08:00
Aaron Culliney
253a99ead2 Ensure tests pass again 2018-11-25 16:19:56 -08:00
Aaron Culliney
b08c5d9289 Silence a firing assert for now 2018-11-25 15:32:24 -08:00
Aaron Culliney
5230afa788 Useability tweak for Millenials and kids ... disk images should be "inserted" read/write by default ;) 2018-11-25 14:45:39 -08:00
Aaron Culliney
1d2f521e0b Fix graphics tearing and stuttering for disk images that sync to video scanner
- Use a second framebuffer to avoid tearing
    - Occasionally stall graphics thread for ~4ms until CPU thread (video scanner) completes drawing a frame
    - These fixes seem to essentially fix graphics issues with Dagen Brock's Flappie Bird (flapple140.po.gz disk image)
2018-11-24 15:16:38 -08:00
Aaron Culliney
db4a71ca6f Enable Chromium systrace for all platforms 2018-11-24 15:15:34 -08:00
Aaron Culliney
27c45834d2 Rename systrace sources 2018-11-23 07:40:37 -08:00
Aaron Culliney
1e2449cfdf Misc Droid updates 2018-11-22 12:08:58 -08:00
Aaron Culliney
3317a23563 Correctly set video preferences 2018-11-22 10:36:28 -08:00
Aaron Culliney
346d7128d8 Fix some issues around disk loading preferences on macOS 2018-11-22 10:14:46 -08:00
Aaron Culliney
0478afe60e Pervasively use full range of signed 16bit samples for speaker 2018-11-22 10:10:24 -08:00
Aaron Culliney
d8e6f54c6b Update README.md 2018-11-21 13:02:21 -08:00
Aaron Culliney
e31a50a7fd Fix a few visual glitches in a hackish way because Android APIs are so damn atrocious 2018-11-20 13:20:06 -08:00
Aaron Culliney
e9ad630996 Android 2.0.0 RC1
- Completely new video scanner
    - NTSC video modes
    - Fast disk loading
2018-11-20 12:36:12 -08:00
Aaron Culliney
d3432fb3d8 Show release notes on first launch/upgrade
- Include a menu item to re-show the release notes
    - Upgrade migration sets new Color TV video mode
2018-11-20 12:34:34 -08:00
Aaron Culliney
3a35404fa3 Enable semi-hackish codepath to reduce audio glitching on fast loading 2018-11-20 12:08:37 -08:00
Aaron Culliney
ab0c796249 Do not go to max speed if no disk image "inserted" 2018-11-20 11:32:28 -08:00
Aaron Culliney
1d089af199 Default enable half scanlines because ... nostalgia :) 2018-11-20 11:31:43 -08:00
Aaron Culliney
6a519b7b5f Tests appear to be working on Droid 2018-11-20 11:30:18 -08:00
Aaron Culliney
8a347630b3 Changes insisted upon by Android Studio 2018-11-18 14:30:17 -08:00
Aaron Culliney
d890cddfa1 Better commentary for new settings 2018-11-18 14:28:54 -08:00
Aaron Culliney
6f03b89283 Misc macOS tweaks 2018-11-18 14:27:17 -08:00
Aaron Culliney
fdc5bd33f0 Fix occasional scanner glitching 2018-11-18 14:24:31 -08:00
Aaron Culliney
aef25292b7 Fix occasional scanner glitching 2018-11-18 13:08:22 -08:00
Aaron Culliney
8b19ba762f Render up to 7 extra scanlines to redraw entire text row when video frame is dirty
- Appears to fix occasional underdraw of bottom row cursor when in 80 column text mode
2018-11-18 12:05:49 -08:00
Aaron Culliney
184884635b Fix compiler warnings in Droid build 2018-11-17 15:57:27 -08:00
Aaron Culliney
92369c3fdb Fix some compiler warnings and tests pass on desktop Linux 2018-11-17 15:52:51 -08:00
Aaron Culliney
0625084602 Avoid deadlock in log rotation 2018-11-17 15:52:14 -08:00
Aaron Culliney
05633d4b0e Unbreak Desktop Linux build 2018-11-17 13:35:35 -08:00
Aaron Culliney
55418504f4 Fix testing after recent video scanner upheaval 2018-11-17 12:39:43 -08:00
Aaron Culliney
8e3c07ed57 Fix some broken preference handling 2018-11-17 11:14:11 -08:00
Aaron Culliney
05d3d884b9 Fix testdisplay after video scanner upheaval 2018-11-17 10:39:28 -08:00
Aaron Culliney
fb44420713 Testing is beginning to work again after upheaval
- testcpu, testvm, testdisk all look good
    - TODO : testdisplay, etc ...
2018-11-11 19:43:49 -08:00
Aaron Culliney
0ddd9ffd91 Use CONFORMANT_TRACKS for macOS build and re-arrange preprocessor macros 2018-11-11 13:18:52 -08:00
Aaron Culliney
80f741f225 Ensure correct pixel adjustment to framebuffer (over)-draw
- Ensures correct starting position for all modes
    - Renders the last 4 samples into the right side overdraw of framebuffer for NTSC modes
2018-11-11 12:17:23 -08:00
Aaron Culliney
aabc29e924 Refresh video after CPU pause
- Fixes rendering if new video mode selected
2018-11-11 12:10:33 -08:00
Aaron Culliney
32d5d2ee1c Avoid a memcpy in graphics pipeline 2018-11-11 08:22:49 -08:00
Aaron Culliney
f235dd6d7c Introduce some one-off memory fixes for poorly-written kracks 2018-11-11 08:22:04 -08:00
Aaron Culliney
ae5a2c4d61 Allow selection of disk images with wrong case extension 2018-11-11 07:30:35 -08:00
Aaron Culliney
dfbc97d7a5 Screen holes should not trigger graphics updates 2018-11-11 07:23:47 -08:00
Aaron Culliney
2928556be8 Optimize video scanner+generator when nothing has been drawn
- Video updates trigger one full frame + one (sub-)scanline re-generate
    - Otherwise video_scannerUpdate() doesn't do much work
2018-11-10 16:49:55 -08:00
Aaron Culliney
fa2f8569f8 Brighter half-scanlines and commentary 2018-11-10 10:28:53 -08:00
Aaron Culliney
a0f2819a4e Misc display fixes 2018-11-10 10:27:05 -08:00
Aaron Culliney
ed37c18ec5 Eliminate branch conditionals in scanner address calculation 2018-11-10 10:26:11 -08:00
Aaron Culliney
18d831b04c Fast disk loading is enabled from a pref 2018-11-07 06:49:16 -08:00
Aaron Culliney
227098ec52 Refactor video/display rendering
- Import NTSC video display modes
    - Migrate to using full-color framebuffers
    - Mac and Android builds somewhat working
    - TODO : likely breaks display testing
2018-11-04 14:07:30 -08:00
Aaron Culliney
8757cb2a06 Name some new prefs 2018-11-04 14:06:59 -08:00
Aaron Culliney
2a7e375f26 Minor refactor joystick prefs 2018-11-04 14:06:38 -08:00
Aaron Culliney
d443f01af3 Bugfix disk6_ioWrite 2018-10-13 07:38:05 -07:00
Aaron Culliney
50d440df1b Fix interpolated color after recent upheaval 2018-08-24 17:41:44 -07:00
Aaron Culliney
9f1ef968ac Improve display testing
- Also test graphics modes in COLOR_MODE_BW
    - Changed TESTHIRES80_2 to merge the two portions of the HIRES80 picture data
    - New TESTLORES_2 and TESTLORES80_2 that draws colors/patterns that better showcase continuous pixel data
2018-08-11 16:32:13 -07:00
Aaron Culliney
6ee8699079 Rename color_mode_t values for clarity 2018-08-11 15:54:00 -07:00
Aaron Culliney
0f3ba15e87 Fix build break on touch devices 2018-08-07 07:57:51 -07:00
Aaron Culliney
5b3d0799a3 Upgrade to newer Android Studio and build tools
- Target Oreo per Goog's dictat
    - Min SDK is now 14 (ICS - 4.0) per build tools requirement
2018-08-07 07:55:59 -07:00
Aaron Culliney
530aaa4f77 Clean up included sample disks 2018-08-07 07:51:31 -07:00
Aaron Culliney
3183dd20b7 Added second LORES80 test screen 2018-08-05 17:25:43 -07:00
Aaron Culliney
55ca51262b First cut at monochrome for LORES80 2018-08-05 17:19:15 -07:00
Aaron Culliney
b403ee7b6e Fix LORES80 shifting algorithm
- Previous variant was not properly truncating bit 9
2018-08-05 17:18:48 -07:00
Aaron Culliney
58488ab8e9 Remove a conditional codepath in LORES40 drawing 2018-08-05 16:00:53 -07:00
Aaron Culliney
0f38791940 Improve a flappy display test 2018-07-29 19:58:59 -07:00
Aaron Culliney
dc49060eda Remove old unused display code 2018-07-29 19:53:41 -07:00
Aaron Culliney
409c4f39ad Update README.md 2018-07-29 19:00:30 -07:00
Aaron Culliney
b216d5f9f8 Include new test disk 2018-07-29 18:47:40 -07:00
Aaron Culliney
75edac3ace First cut at implementing conformant video scanner 2018-07-29 18:33:17 -07:00
Aaron Culliney
43ab5c8233 Implement a generic debugger hook to break stepping 2018-07-29 16:43:36 -07:00
Aaron Culliney
14bb75a941 Misc testing tweaks 2018-07-29 16:42:33 -07:00
Aaron Culliney
596f767b9a Updated generated file 2018-07-29 16:33:07 -07:00
Aaron Culliney
8f4e878919 Allow ignoring font mode in conversion 2018-07-29 16:31:10 -07:00
Aaron Culliney
a8e14381c1 Ensure disk_byte is properly reinitialized
- Improves CPU tracing against baseline
2018-07-29 16:28:56 -07:00
Aaron Culliney
7ac8f4303c Repurpose a softswitch printing function 2018-07-29 16:26:07 -07:00
Aaron Culliney
db8fb67944 Help Xcode/lldb cpu thread debugging on Intel by zeroing out the clobbered base pointer 2018-07-29 13:51:36 -07:00
Aaron Culliney
850b8f3b02 Include moar thingz, not necessarily for use by Apple2{Mac,iOS} ... 2018-05-06 10:15:03 -07:00
Aaron Culliney
e3c0c2550f Changes insisted upon by XCode 2018-05-06 10:14:56 -07:00
Aaron Culliney
aa41c89e68 Lite refactor video timing 2018-05-06 10:14:47 -07:00
Aaron Culliney
6ed0100291 More explicitly enable assert() in Droid release builds 2018-04-22 08:44:02 -07:00
Aaron Culliney
3318d159b5 mark expected assertion codepath 2018-04-22 08:43:57 -07:00
Aaron Culliney
234caa8c9c Consolidate hires page write handlers 2018-04-22 08:43:53 -07:00
Aaron Culliney
eb13718c5e Introduce CPU thread function annotation and enforcement 2018-04-22 08:43:16 -07:00
Aaron Culliney
51d2efba03 Streamline CPU tracing codepaths and remove some deadc0de 2018-03-31 12:48:14 -07:00
Aaron Culliney
1de71d1ff4 monocolor mode for LORES 2018-03-31 12:27:50 -07:00
Aaron Culliney
3c04e12db0 Add proper monocolor mode for HIRES80 2018-03-11 16:30:17 -07:00
Aaron Culliney
cedf7b9d0e Minor clean-up deadc0de in HGR40 generation 2018-03-11 16:28:07 -07:00
Aaron Culliney
154b9a7ef8 Fix Apple2Mac build 2018-01-22 07:33:33 -08:00
Aaron Culliney
dd42645c4c Silence some Xcode compiler warnings 2018-01-22 07:33:06 -08:00
Aaron Culliney
e898a85f50 Remove unneeded i86 assembly and fix iOS simulator build 2018-01-21 12:41:07 -08:00
Aaron Culliney
807b441ab1 Partial revert of "Mac app and tests build again"
This partially reverts commit 8f155b5190.
2018-01-20 17:42:04 -08:00
Aaron Culliney
4aa75a59c8 Change to automatic signing 2018-01-20 09:14:33 -08:00
Aaron Culliney
c1076aa9cf Upgrade to recommended Xcode settings and fix some build issues 2018-01-20 08:25:51 -08:00
Aaron Culliney
8f155b5190 Mac app and tests build again 2018-01-20 08:14:08 -08:00
Aaron Culliney
e4e0c941d3 Refactor CPU assembly for simplicity and efficiency
- Variables needed in assembly now accessible in a struct pointer that  avoids __PIC__ nastiness
    - Reduces code size and execution time for CPU thread
2018-01-15 16:19:21 -08:00
Aaron Culliney
6e7af373d7 Changes insisted upon by Android Studio 2018-01-15 10:17:46 -08:00
Aaron Culliney
167263ab3a Ensure starting with clean buffer ... 2018-01-15 10:17:02 -08:00
Aaron Culliney
b1161ba746 Bump Android version to 1.2.2 2017-12-03 13:44:04 -08:00
Aaron Culliney
6b901dff07 Avoid thread race with testprefs.c 2017-12-03 11:34:11 -08:00
Aaron Culliney
8b554083b4 Use "inverse" for keyboard selection color... 2017-11-26 16:20:05 -08:00
Aaron Culliney
cd6eb65fc5 Ensure color preferences set before any messaging on startup 2017-11-26 13:35:33 -08:00
Aaron Culliney
4bc61b6216 Allow access to system soft touch keyboard from top right menu 2017-11-26 13:35:04 -08:00
Aaron Culliney
20b085d9f7 Ensure that HUD color default matches true default 2017-11-26 11:35:10 -08:00
Aaron Culliney
a0c26386e8 malloc returns pointer ;) 2017-11-18 11:38:46 -08:00
Aaron Culliney
1ff7bfe4bf Use JDK 1.8 for the Java thingz 2017-11-18 11:28:55 -08:00
Aaron Culliney
7a99b72f20 Allow different colorschemes with HUD devices 2017-11-11 16:02:38 -08:00
Aaron Culliney
021604471b Avoid potential NPE 2017-11-11 12:06:26 -08:00
Aaron Culliney
dc5e7eee25 Allow showing system soft touch keyboard 2017-11-11 11:59:20 -08:00
Aaron Culliney
c3ea8c5aea Streamline configure checks and display disabled A/V systems 2017-09-28 16:28:10 -07:00
Aaron Culliney
53459c9e78 Clean up a number of Xcode build warnings 2017-09-28 16:27:12 -07:00
Aaron Culliney
570147712c Get Mac and iOS builds working again
- iOS build is still super Alpha ;)
2017-09-13 20:27:42 -07:00
Aaron Culliney
a9755215bf Native code builds again on Droid 2017-09-13 20:25:27 -07:00
Aaron Culliney
981cff845b Also convert '\n' to SCODE_RET 2017-09-09 18:08:49 -10:00
Aaron Culliney
b5b79faf1d First cut at CLI ncurses video renderer
- Currently supports 40/80col TEXT modes
    - TODO : graphics scaling
2017-09-09 18:06:43 -10:00
Aaron Culliney
dd02333eae Convert raw emulator key data to ASCII (or mousetext) 2017-09-09 16:59:55 -10:00
Aaron Culliney
a055ae8b8d Minimally begin to support UTF8 2017-09-09 16:57:06 -10:00
Aaron Culliney
25e4fd0eba Allow querying whether the classic interface is showing 2017-09-09 16:55:39 -10:00
Aaron Culliney
58d2392eac Name the running man glyph 2017-09-09 16:54:43 -10:00
Aaron Culliney
ae76537a19 Name colormap indices 2017-09-09 16:53:10 -10:00
Aaron Culliney
8e6701bcbb Allow CLI dynamic choice of A/V backends 2017-09-09 15:35:00 -10:00
Aaron Culliney
2517b45720 Silence a compile warning 2017-08-27 19:18:15 -10:00
Aaron Culliney
d35d87af9f Call open() with reasonable mode setting 2017-08-27 19:17:32 -10:00
Aaron Culliney
2d5c47d534 Autotools builds on Mac somewhat 2017-08-21 17:56:20 -10:00
Aaron Culliney
85dc4e5132 Move some sources into meta directory 2017-08-19 19:57:23 -10:00
Aaron Culliney
861cb3320c Display further refactored for text mode callbacks 2017-08-15 18:44:49 -10:00
Aaron Culliney
d226db8021 Better naming in font loading functions 2017-08-15 18:43:48 -10:00
Aaron Culliney
f8b4602fca Beginning to refactor display and backend video
- Futher disentangle display, interface, and video backends
    - Backend video owns the staging/intermediate framebuffer for now
    - Add the beginnings of display update callbacks
2017-08-06 12:12:12 -10:00
Aaron Culliney
0801c9f010 Be moar pessimistic about VAO availability
- TODO FIXME : refactor to dynamically check this
2017-07-31 17:49:53 -07:00
Aaron Culliney
72c5c550fe Always hide the system soft touch keyboard on Droid 'cause that keyboard is likely spying on you and we don't need it to party like it's 1987 :P 2017-07-31 17:49:29 -07:00
Aaron Culliney
11cf51753b Fix Desktop and Android builds after headless upheaval 2017-07-31 17:47:58 -07:00
Aaron Culliney
98aecedd65 Null renderer build can now execute tests 2017-07-30 12:42:31 -10:00
Aaron Culliney
bcbf5ac234 Refactor LOG() facilities
- Enable logging to file(s)
    - Enable log rotation
    - Allow silencing console logging (e.g., to stderr)
2017-07-30 12:24:48 -07:00
Aaron Culliney
1716dd35be Debugger and other meta code is now part of core emulator build 2017-07-30 10:11:47 -07:00
Aaron Culliney
153f1434db Rename GL_ERRLOG() to GL_MAYBELOG() 2017-07-15 15:08:42 -10:00
Aaron Culliney
51a5f5fcf7 Excise ERRLOG() in favor of LOG() 2017-07-15 14:39:15 -10:00
Aaron Culliney
3b1c72e872 Excise RELEASE_LOG() and RELEASE_BREAK() macros
- LOG() and assert() work just fine ;)
2017-07-15 14:19:31 -10:00
Aaron Culliney
3c1dcd4a69 Refactor to enable loading multiple AV backends
- Includes default 'null' backends with lowest priority
2017-07-15 13:25:00 -10:00
Aaron Culliney
97a98f0c86 Force X11 scaling to 2X and remove option for now 2017-07-15 13:16:50 -10:00
Aaron Culliney
bdbe544c3b Avoid SIGSEGV if glCheckFramebufferStatus not available 2017-07-09 15:41:43 -07:00
Aaron Culliney
13bf93e5ca Build release mode tests with NDEBUG=1 2017-07-09 11:09:11 -10:00
Aaron Culliney
2836d903d9 Bump Android version to 1.2.1 2017-07-09 07:24:24 -10:00
Aaron Culliney
a09d08c079 Changes insisted upon by Android Studio 2017-07-08 14:53:02 -10:00
Aaron Culliney
4885388a2d Bump Android version to 1.2.0 2017-07-08 14:51:11 -10:00
Aaron Culliney
aefe243620 Work around mysterious occasionally failing assert 2017-07-08 14:50:39 -10:00
Aaron Culliney
5102706441 Ensure that Apple2ix handles invalid gzipped files 2017-07-07 17:57:19 -10:00
Aaron Culliney
55cba116e4 Revert adding A2V3 format as it is unnecessary
- Re-gzipping ejected images is based solely on the file extension and not the actual file stream contents
    - Read/write disk images with extension ".gz" are re-gzipped in-place upon ejection
    - Read/write disk images without extension ".gz" are not changed upon ejection
    - Read-only disk images maintain current behavior (no modifications performed at all)
2017-07-06 19:36:37 -10:00
Aaron Culliney
f8b570869f Ensure we use run-as 2017-07-04 08:02:42 -10:00
Aaron Culliney
ccfaa0544a Explicitly test A2V3 data 2017-07-04 08:01:02 -10:00
Aaron Culliney
c00a52cbc6 unbreak testui 2017-07-04 08:00:52 -10:00
Aaron Culliney
b9c4ec4cc3 HACK : bump the timeout to get test passing on slow devices 2017-07-04 08:00:36 -10:00
Aaron Culliney
d872ad3cdd Add link to licenses page 2017-07-01 11:50:40 -10:00
Aaron Culliney
c30c50be06 Minor renaming of internal API function 2017-07-01 11:43:00 -07:00
Aaron Culliney
25a0f69a5f Clean up disk selection 2017-07-01 11:40:38 -07:00
Aaron Culliney
7411a987fa Disk chooser can now also choose .a2state files
- Internal save/restore API now uses file descriptors (supports restrictive app environments)
2017-07-01 11:03:15 -07:00
Aaron Culliney
d98c4afa84 Rename emulator.state to emulator.a2state and handle migration 2017-06-28 22:05:11 -07:00
Aaron Culliney
dacf0de80e More nonsensical changes that Android Studio insists upon making 2017-06-25 16:16:54 -07:00
Aaron Culliney
8e91d1f7de Clean up some Android cruft that we don't want to support (for now, possibly ever) because of platform fragmentation and rockstarz and ninjaz 2017-06-25 16:06:54 -07:00
Aaron Culliney
dd0de51d64 Disentangle new-school and old-school disk selection and misc cleanup 2017-06-13 09:51:52 -07:00
Aaron Culliney
2a263e2418 Avoid a crash in nativeStateExtractDiskPaths() 2017-06-07 02:21:48 -07:00
Aaron Culliney
c731c2a310 Do not show disk insertion dialog if nothing chosen 2017-06-05 16:41:17 -07:00
Aaron Culliney
e020817068 Bump Gradle version 2017-06-05 16:06:19 -07:00
Aaron Culliney
91fdf7b8e5 Add ability to choose disk images via the Android system chooser 2017-06-05 16:05:44 -07:00
Aaron Culliney
55ec0c7034 Unbreak Linux desktop build 2017-06-03 05:40:17 -07:00
Aaron Culliney
033dbf71ea Clean up disk/zlib internal APIs 2017-06-03 05:40:09 -07:00
Aaron Culliney
0468cea2d4 Clarify disk/zlib internal API comments after recent upheaval 2017-05-29 08:05:44 -10:00
Aaron Culliney
b300e60e2a Persist disk image 'was_gzipped' state
- 'was_gzipped' state is persisted in .apple2.json preferences
    - This removes all file-renaming codepaths at the cost of temporarily nonconformance to naming convesion (e.g., disk
      images inserted read/write are still called 'foo.dsk.gz' although they are not compressed)
    - Unclean shutdown of emulator leaves any disk images that were inserted read/write in their non-gzipped state
      (regardless of file name extension) on disk until emulator restarted and disk images explictly ejected or clean
      shudown.  App removal after unclean shutdown will potentially leave mis-named disk images lingering.
    - Emulator will handle edge cases of non-gzipped disk images with '.gz' extension and gzipped disk images without
      '.gz' extension
2017-05-29 08:05:38 -10:00
Aaron Culliney
399daf16fa More changes insisted upon by Android Studio 2017-05-28 10:09:14 -10:00
Aaron Culliney
8873fe09d1 U5 soft touch keyboard 2017-05-28 08:51:30 -10:00
Aaron Culliney
5dfa2e8797 Changes insisted upon by Android Studio 2017-05-28 08:49:38 -10:00
Aaron Culliney
4c893cc197 Beginnings of trace testing on Droid
- Currently broken because !CONFORMANT_TRACKS
2017-05-28 08:48:24 -10:00
Aaron Culliney
e1f0557b87 Allow loading test images from /sdcard/apple2ix on Android 2017-05-28 08:48:21 -10:00
Aaron Culliney
3de4a181eb Silence some build warnings on Android 2017-05-28 08:48:16 -10:00
Aaron Culliney
9bd59661ed Refactor disk image UI to use file descriptors and not paths
- Push path opening responsibility out of disk6 API in favor of using file descriptors
    - Improves handling of readonly gzipped disk images (now data is read without gunzipping or mmap()ing the file)
    - Currently represents an API-breaking change for macOS, iOS, GNU/Linux desktop ports (but should be easy to fix)
2017-05-28 08:48:11 -10:00
Aaron Culliney
2ca742650f Remove Intent handling of disk paths in preparation for refactoring this 2017-05-28 08:48:04 -10:00
Aaron Culliney
31aca92ffd video flags are unsigned long 2017-05-28 08:47:59 -10:00
Aaron Culliney
6bfbe3cc88 Bump Android version to 1.1.10 2016-11-07 19:37:24 -08:00
Aaron Culliney
14a40e055e Merge remote-tracking branch 'origin/droid-1.1.8.1' into aaron_experimental_rebase
Conflicts:
	Android/app/build.gradle
2016-11-07 19:36:59 -08:00
Aaron Culliney
bf0e61c9ea Android patch release 1.1.8.1 2016-11-05 11:43:42 -07:00
Aaron Culliney
96611beab9 Revert "Avoid doing Mockingboard work as much as possible"
This reverts commit 1bb3f75a06.

    - Caused excessive glitching in U4 music
2016-11-05 10:13:35 -07:00
Aaron Culliney
086c7e585a Update Android Studio 2016-10-30 12:18:23 -07:00
Aaron Culliney
a13caa8bd1 Bump Android version to 1.1.9 2016-10-30 11:43:23 -07:00
Aaron Culliney
6a630714ed Update README.md 2016-10-26 21:31:42 -07:00
Aaron Culliney
9cc3603d73 HACKishly fix macOS/iOS build 2016-10-26 21:23:34 -07:00
Aaron Culliney
c0a4ced573 Silence a LOG 2016-10-23 18:30:08 -07:00
Aaron Culliney
1bb3f75a06 Avoid doing Mockingboard work as much as possible
- Fixes audio underflow issue on ancient Droid devices
2016-10-23 17:40:35 -07:00
Aaron Culliney
ddcef734c7 Disable excessive IRQ checking for now 2016-10-23 17:39:00 -07:00
Aaron Culliney
07c2f3cba8 Fix issue that stricter compiler warned about 2016-10-23 17:38:36 -07:00
Aaron Culliney
d6656a4fff Insure that preferences are loaded when handling early crashes 2016-10-23 12:11:15 -07:00
Aaron Culliney
2f8381e4df Don't run lint in Android release builds for now
- This currently blows up if we're missing I18N/L10N strings for a [currently] non-supported translation
    - We have our own release procedure that should be sufficient at this point
2016-10-22 12:05:01 -07:00
Aaron Culliney
85668fbe35 Moar changes insisted upon by Android Studio 2016-10-22 12:04:14 -07:00
Aaron Culliney
db04d330c7 Expose save-state file in /sdcard/apple2ix 2016-10-22 11:41:16 -07:00
Aaron Culliney
bea9e1ee31 Added new iOS testing targets and misc project changes 2016-10-16 21:30:47 -07:00
Aaron Culliney
3def67975c Avoid testing certain fragile/non-portable disk state 2016-10-16 19:20:23 -07:00
Aaron Culliney
300c292ec5 HACKishly avoid clock_gettime() build issue with Xcode 8 2016-10-16 19:17:20 -07:00
Aaron Culliney
a6516fc1ad Changes that Android Studio insists upon 2016-10-16 16:01:01 -07:00
Aaron Culliney
d2e61e5091 Ensure big-endian data in A2V2 save-file format
- Also sanity-check buffer read overflow during loading
2016-10-16 16:00:14 -07:00
Aaron Culliney
f6e2ebc028 Add testui target for Android 2016-10-15 19:29:15 -07:00
Aaron Culliney
039063d9b0 Fix test build break 2016-10-15 19:16:12 -07:00
Aaron Culliney
97d538c4d2 Hackishly fix issue on x86 Android __PIC__ builds 2016-10-15 19:07:43 -07:00
Aaron Culliney
5ab68b5503 Fix a broken test on Android 2016-10-15 19:07:10 -07:00
Aaron Culliney
a417249691 Add more tests for save/restore feature 2016-10-15 13:36:03 -07:00
Aaron Culliney
1be2c6fd27 A2V2 save format also saves timing information 2016-10-15 13:34:14 -07:00
Aaron Culliney
37df740fd3 Add save/load state to mockingboard 2016-10-15 13:34:03 -07:00
Aaron Culliney
bb93b5c243 Adjust CPU trace file format
- CPU_TRACING now records IRQ check timeout and total cycles
2016-10-09 12:23:48 -07:00
Aaron Culliney
c80e16de3b Unbreak tests on Android 2016-10-09 12:21:41 -07:00
Aaron Culliney
0be555d7e9 Fix cycle counts in CPU tests after upheaval 2016-10-09 12:20:07 -07:00
Aaron Culliney
1294485a02 Beginnings of a pluggable peripheral card API and fix a CPU boot tracing divergence against AppleWin
- In particular, return floating_bus() for non-plugged in cards when accessing the $C100-$C700 range
2016-10-09 12:19:32 -07:00
Aaron Culliney
ccd05e52fe Improve timing conformance in Mockingboard
- Correctly calculate 7 cycles for handling IRQs
    - More frequent calls to MB_UpdateCycles()
    - Correctly calculate timing for MB_EndOfVideoFrame() when testing/tracing/debugging
2016-10-09 12:19:21 -07:00
Aaron Culliney
4fd6a87340 Pinpoint IRQ requests in CPU tracing 2016-10-02 14:24:04 -07:00
Aaron Culliney
3edb24ea3b Change cycle counts to conform with AppleWin 2016-10-02 14:23:59 -07:00
Aaron Culliney
3a6c033077 Improve MB_TRACING adherence to baseline 2016-10-02 13:59:15 -07:00
Aaron Culliney
d0b3f632e1 Further improve determinism for Mockingboard tracing 2016-10-02 13:58:50 -07:00
Aaron Culliney
4bdabcaa9a Optimize disk6 track switch read/write and improve tracing conformance with AppleWin
- No need to read/write if only changed to a "half track"
    - Fixes one small glitch in boot disk tracing
2016-10-02 13:58:06 -07:00
Aaron Culliney
c4e3dccc38 Rename disk tracing API 2016-10-02 13:56:56 -07:00
Aaron Culliney
ba09a6aba9 Refactor Disk ][ API to improve tracing conformance with AppleWin
- Binary stability change/fix : an 0xEB -> 0xFF in the sync bytes of blank.nib.gz
2016-10-02 13:55:05 -07:00
Aaron Culliney
faab9fa04a Rename external directory to something GitHub recognizes
- Should hopefully force recognition as a 'C' repository rather than counting all the Breakpad C++
2016-09-24 12:44:50 -07:00
Aaron Culliney
fe63695f81 Build and fix testui on Desktop Linux 2016-09-17 13:05:26 -07:00
Aaron Culliney
6daa1aa010 Break the build if external ROMz cannot be loaded 2016-09-17 12:42:46 -07:00
Aaron Culliney
781cc60d8c Assert CPU paused when switching disk images 2016-09-17 12:24:47 -07:00
Aaron Culliney
88ca414f80 CPU thread housekeeping on shutdown 2016-09-17 12:11:10 -07:00
Aaron Culliney
5805822bd4 Android : bring in scripts needed by toolchain_edits/ndk-gdb from NDK r10e 2016-09-15 10:07:56 -07:00
Aaron Culliney
8c244486e8 Fix a bug in emulation state restoration that caused disk images to be inserted read/write 2016-09-11 11:55:24 -07:00
Aaron Culliney
6e09383504 Add testui target for Apple2Mac project 2016-09-11 11:54:57 -07:00
Aaron Culliney
e479a861d7 First cut at testing save/restore of emulation state 2016-09-11 11:53:59 -07:00
Aaron Culliney
07e1c4ed83 Misc test suite improvements 2016-09-11 11:51:55 -07:00
Aaron Culliney
b37c94f404 Remove unused test files 2016-09-11 06:28:24 -07:00
Aaron Culliney
3ba0f43dc5 Hook up a few more icons in Apple2Mac 2016-09-11 06:27:27 -07:00
Aaron Culliney
57dd816d61 Force CONFORMANT_TRACKS behavior for testtrace 2016-09-10 19:15:45 -06:00
Aaron Culliney
daddf5f287 Misc fixes for Mac testing 2016-09-10 16:57:52 -06:00
Aaron Culliney
b04e4ab348 Reference external ROM directory 2016-09-10 16:21:38 -06:00
Aaron Culliney
bc8091cde3 Fix Linux Desktop builds 2016-09-10 16:19:49 -06:00
Aaron Culliney
f3324c0b2b Placate/fool Clang static analysis in DDHidLib 2016-09-10 11:35:20 -06:00
Aaron Culliney
cb31ccaf9c Fix a bunch of Clang static analysis issues 2016-09-10 11:32:33 -06:00
Aaron Culliney
fc3df8c6b6 Add macOS testtrace target
- TODO FIXME : currently failing some tracing tests
2016-09-02 12:39:38 -07:00
Aaron Culliney
721ea2c172 Also copy external-disks and use them if available 2016-09-02 12:39:09 -07:00
Aaron Culliney
99953ea90a Fix genglue for paths with spaces 2016-08-27 15:18:26 -07:00
Aaron Culliney
8921f79a14 Beginnings of Mockingboard tracing for automated testing 2016-08-27 13:33:05 -07:00
Aaron Culliney
5883545390 Ensure CPU speeds in test prefs are correctly set 2016-08-27 12:10:36 -07:00
Aaron Culliney
d4581eb492 Enable testing of 3rd-party disk images 2016-08-27 09:17:23 -07:00
Aaron Culliney
94ca64f08d Avoid a segfault if shader is not found ('make install' not run) 2016-08-27 09:17:16 -07:00
Aaron Culliney
74457c4cda Misc Fixes for Mac and iOS builds 2016-08-21 08:54:07 -10:00
Aaron Culliney
bc46b2d24e Update Android Studio and components 2016-08-21 08:53:58 -10:00
Aaron Culliney
35e3d6a969 Fix building with stricter Android NDK gcc 4.9 2016-08-21 08:53:39 -10:00
Aaron Culliney
1a6c1292f3 Fix Android native build script on Mac
- Darwin/BSD 'wc' utility injects extra whitespace, so strip it out
2016-08-21 08:51:15 -10:00
Aaron Culliney
838bf0496a Include Mac breakpad binaries for CLI native builds from Mac development box
- NOTE : Darwin dump_syms is really 'dump_syms_elf' from my copy of Breakpad
2016-08-21 08:51:05 -10:00
Aaron Culliney
b4c72c15c6 Formally disable SSI263 (speech synthesis) codepaths for now
- SSI263Thread was previously disabled (doesn't appear to be used for most games, e.g., U4 and U5)
    - As written this code would initialize 64 backend audio buffers ... this seems excessive, especially for low-end
      platforms we support (e.g., Android 2.3.3)
    - My limited understanding of these codepaths would suggest that only one phoneme should play at a time so
      presumably we should only need one additional backend audio buffer
    - We may also need to augment the soundcore/backend API to support playing/stopping these "one-shot" phonemes
    - Added assert(false) for DEBUG builds to discover which disk images blow up
2016-07-31 12:59:33 -07:00
Aaron Culliney
fe894b6ac2 Clarify codepaths for my Windoze-weary eyes 2016-07-25 21:22:36 -07:00
Aaron Culliney
1f79edca1d Fix Mac/iOS build 2016-07-24 18:13:39 -07:00
Aaron Culliney
eeba82ba24 Merge Mockingboard implementation from upstream AppleWin 2016-07-24 17:55:50 -07:00
Aaron Culliney
7a82bbf471 Unbreak Mockingboard codepaths 2016-07-24 17:55:07 -07:00
Aaron Culliney
7ad7b65d11 Add speaker tracing API and tests 2016-07-23 17:35:11 -07:00
Aaron Culliney
2f156d4262 Improve genglue.sh script for generating trampoline functions 2016-07-23 17:24:10 -07:00
Aaron Culliney
fc9ad6d4e1 Excise unused preprocessor macro 2016-07-23 17:23:58 -07:00
Aaron Culliney
cc9a164bec Audio codepaths now always enabled
- Supported platforms:
        - Mac/iOS (OpenAL)
        - Desktop Linux/POSIX (OpenAL)
        - Android (OpenSLES)
    - TODO : for POSIX builds we should provide our own "null" audio backend if OpenAL/OpenSL not found by build script
2016-07-23 17:23:54 -07:00
Aaron Culliney
06f8015959 Misc fixes for test stubs 2016-07-23 17:23:36 -07:00
Aaron Culliney
19178b751c Unbreak tests on desktop Linux 2016-07-22 20:54:21 -07:00
Aaron Culliney
fc5734fedc Updates that Xcode InterfaceBuilder insists upon making because ... progress 2016-07-22 20:43:55 -07:00
Aaron Culliney
4457e9e722 Tests running and passing again on iOS devices 2016-07-22 20:43:47 -07:00
Aaron Culliney
59e6c4fabc Fix HOMEDIR on mobile devices 2016-07-22 20:43:42 -07:00
Aaron Culliney
b4925292e1 iOS improvements
- Recursively copy disks directory out of bundle to a R/W location in Documents directory
    - Allow background audio to play
2016-07-22 20:43:38 -07:00
Aaron Culliney
635075f34b iOS port is beginning to build again 2016-07-15 21:23:37 -07:00
Aaron Culliney
ab3392a8f8 Clean up macOS and iOS project file
- Include external disks
    - Disks loading from App bundle is still broken on iOS (FIXME TODO)
2016-07-15 21:23:29 -07:00
Aaron Culliney
8d2142592c Use correct deadc0de team 2016-07-15 21:23:24 -07:00
Aaron Culliney
ed8942f82d Fix testtrace by testing with more determinism ;) 2016-07-15 20:28:23 -07:00
Aaron Culliney
bfb7ab3d5f Remove unnecessary codepaths for testdisk 2016-07-15 20:28:23 -07:00
Aaron Culliney
d7413dae01 Allow for more deterministic typing/stepping of CPU thread from debugger 2016-07-15 20:28:23 -07:00
Aaron Culliney
4ca642a715 Remove duplicate code paths for stepping/typing-into the debugger 2016-07-15 20:28:23 -07:00
Aaron Culliney
b62f5e9e7f Hackishly enforce determinism to trace testing 2016-07-15 20:28:23 -07:00
Aaron Culliney
ddd03ca4d0 Fix typo 2016-07-09 10:51:17 -07:00
Aaron Culliney
c393ea81a0 Simplify code paths for POSIX desktop testing and fix testcpu breakage 2016-07-02 13:44:59 -07:00
Aaron Culliney
4fee1ee0be Fix a test break when converting to http[s] URL 2016-07-02 13:35:20 -07:00
Aaron Culliney
b0a2a34d6a Ensure POSIX desktop builds for all tests use proper CFLAGS
- Previously when compiling against conditionally-included "subdir/foo.o" we would pick up the apple2ix CFLAGS
      (without -DTESTING=1 and other necessary flags)
    - Now when compiling against conditionally-included "subdir/sometestexe-foo.o" we pick up the correct "testexe" CFLAGS
    - Likely there is a less HACKish way to do this, but I lack the google-fu to discover the superior incantation ;P
2016-07-02 13:31:03 -07:00
Aaron Culliney
7af88b3568 Tests are starting to run in iOS simulator 2016-06-30 21:03:58 -07:00
Aaron Culliney
3b6b568fc2 Ensure we use the correct device bounds 2016-06-26 17:17:28 -07:00
Aaron Culliney
2560a45e37 Get iOS app minimally building 2016-06-26 17:16:21 -07:00
Aaron Culliney
4aa78f7a65 Update Android Studio components 2016-06-26 11:29:41 -07:00
Aaron Culliney
d3b87e5c05 Update various copy 2016-06-26 09:55:13 -07:00
Aaron Culliney
19d427ac6e Merge remote-tracking branch 'origin/develop' 2016-06-26 09:34:00 -07:00
Aaron Culliney
ca73750c44 Various tests works again on Mac 2016-06-26 09:31:56 -07:00
Aaron Culliney
f4354e2171 Get CPU test building again Mac 2016-06-24 19:33:13 -07:00
Aaron Culliney
e06620b57d http -> https 2016-06-19 12:43:35 -07:00
Aaron Culliney
bc6719d712 Allow access to toplevel of external storage 2016-06-12 09:19:02 -07:00
Aaron Culliney
0eb4dad0da More fixes to get iOS build unstuck 2016-05-15 18:22:58 -07:00
Aaron Culliney
fd5b97189a HACK : get iOS building again
- TODO FIXME : actually get proper __PIC__ code written for Mac/i386
2016-05-15 18:13:20 -07:00
Aaron Culliney
b7a49137c0 AndroidStudio updated 2016-05-11 21:11:54 -07:00
Aaron Culliney
54712f8423 Bump Android version to 1.1.8 2016-05-11 21:05:42 -07:00
Aaron Culliney
8ce1fffe89 Fix x86 __PIC__ bug when indirecting in GLUE_BANK_MAYBEREAD() 2016-05-10 22:17:37 -07:00
Aaron Culliney
7dc83521ec Show visual spacebar glyph when custom selecting keypad-joystick controls 2016-05-09 23:00:26 -07:00
Aaron Culliney
28ecb323ba Be more resilient in tap/swipe gesture handler 2016-05-09 22:03:23 -07:00
Aaron Culliney
ac912ffd54 Ensure an intelligent default setting for mockingboard volume 2016-05-08 21:38:41 -07:00
Aaron Culliney
95e7964912 Revert "Remove Android Studio file that always changes from revision control"
This reverts commit 9f1f6e4fe1.

    - OK apparently we do need to keep an eye on this, because Android Studio will clobber it every so often and result
      in a "weird" state where it can't build the project...
2016-05-08 14:33:03 -07:00
Aaron Culliney
abfa461227 Misc stuff for Android build 2016-05-08 13:39:17 -07:00
Aaron Culliney
b8ab6e2be0 Fix testcpu build break when targeting latest NDK 2016-05-08 12:33:00 -07:00
Aaron Culliney
9f1f6e4fe1 Remove Android Studio file that always changes from revision control 2016-05-08 12:16:22 -07:00
Aaron Culliney
093c99466f Various tweaks to get release build working 2016-05-08 12:06:57 -07:00
Aaron Culliney
254e013342 Migrate legacy Android-managed preferences to our custom JSON preferences 2016-05-08 12:06:31 -07:00
Aaron Culliney
b0c1703b23 Send JSON preferences in crash report 2016-05-07 16:33:51 -07:00
Aaron Culliney
9e9d0333cc Ensure that crash prompt shows up when in portrait mode
- (Android lifecycle change was killing our previous Alert!)
2016-05-07 16:33:23 -07:00
Aaron Culliney
dadf5e02a1 Persist the current soft touch keyboard variant 2016-05-06 21:17:02 -07:00
Aaron Culliney
44b2e564d4 Bugfix keyboard min alpha 2016-05-06 20:38:18 -07:00
Aaron Culliney
edaab3e110 Experiment with -Os optimization level 2016-05-05 21:55:12 -07:00
Aaron Culliney
2de03fd849 Stuff changed by Android Studio 2016-05-05 21:54:45 -07:00
Aaron Culliney
7d21391efe Bugfix : force a full load of preferences when going to background 2016-05-05 21:50:47 -07:00
Aaron Culliney
aad2f0a71e Speculative fix from a crash report from the wild 2016-05-05 21:06:52 -07:00
Aaron Culliney
ab59b089d9 Revert to using GCC 4.9 toolchain for Android because GCC can x86!
- While LLVM/Clang appears to be very stable with codegen for ARM architectures, I have trust issues with Clang
      producing optimized codegen for the 32bit x86 architecture.
    - I have witnessed a number of examples where Clang generates invalid x86 code for both Chromium Breakpad and JSMN
      in this project, as well as similar issues discovered at the day job.  (This is for the LLVM toolchain in the
      latest NDK as well as previous ones)
2016-05-05 20:45:24 -07:00
Aaron Culliney
d2261815a1 Do not build for Android x86_64
- I have only 32bit native and 64bit-running-PAE devices for testing
    - Paraphrasing Billy G, "32 bits ought to be enough for anybody"
2016-05-04 21:48:57 -07:00
Aaron Culliney
13701b5497 Add a few more crash tests for Android 2016-05-04 21:48:27 -07:00
Aaron Culliney
fd0611f3a1 update dump_syms and minidump_stackwalk Linux binaries
- dump_syms in particular is needed for the Android build "generate symbols" step
2016-05-04 21:44:53 -07:00
Aaron Culliney
85d1af95f6 Merge commit 'fd3708f0df4531cbf164bb744d114466328ce55e' as 'externals/breakpad' 2016-05-03 22:23:53 -07:00
Aaron Culliney
fd3708f0df Squashed 'externals/breakpad/' content from commit 06c7bd9
git-subtree-dir: externals/breakpad
git-subtree-split: 06c7bd9f5d252d03689ca9f6caca96925d22a10c
2016-05-03 22:23:53 -07:00
Aaron Culliney
36626fc201 Excise Breakpad to bring in new rebase branch 2016-05-03 22:23:17 -07:00
Aaron Culliney
3c45c455ec Upgrade Android Studio 2016-05-03 21:05:49 -07:00
Aaron Culliney
70e203303e HACK : fix a test suite breakage caused by joystick button reset delay 2016-05-02 22:14:42 -07:00
Aaron Culliney
7b3eabb12f Fix x86 and x86_64 __PIC__ code for Linux and Mac 2016-05-01 18:49:18 -07:00
Aaron Culliney
da4e5eb75a Get testtrace building again on Desktop Linux
- These tests are currently somewhat unstable, TODO FIXME
2016-04-26 21:10:16 -07:00
Aaron Culliney
30b203e3da Changes insisted upon by Android Studio 2016-04-24 18:31:32 -07:00
Aaron Culliney
e5b3099b04 Improved clamping of Mac joystick/gamepad axes 2016-04-23 14:56:05 -07:00
Aaron Culliney
747a469241 Fixes to get Mac build working again
- TODO : various settings appear to be broken, but basic emulator functionality appears to be working ;-)
2016-04-23 14:54:46 -07:00
Aaron Culliney
e7b2dd3ad1 Fix product name 2016-04-23 12:46:59 -07:00
Aaron Culliney
28cdb24d18 Move an assert to avoid firing on load of Mac app 2016-04-23 12:38:55 -07:00
Aaron Culliney
14108fb97b Fix Mac assembly 2016-04-23 11:52:20 -07:00
Aaron Culliney
ae75ce2798 Avoid calling glGetError() on thread without OpenGL context 2016-04-23 11:51:02 -07:00
Aaron Culliney
98e5c883f9 Project file changes 2016-04-23 10:16:29 -07:00
Aaron Culliney
a77075c881 Set a lower limit to avoid thread starvation 2016-04-22 20:19:40 -07:00
Aaron Culliney
90b52702d6 Refactor __PIC__ codepaths on x86_64 2016-04-21 22:09:04 -07:00
Aaron Culliney
a5a498f96c Tweaks for arch builds 2016-04-21 22:06:42 -07:00
Aaron Culliney
b2acb4a1ed Ensure we don't unnecessarily sync preferences to native 2016-04-18 21:30:01 -07:00
Aaron Culliney
2bf379aad4 Improve Android keypad joystick selection routine
- Changes Skip button to not set anything
    - Adds a None button to explicitly choose that
    - Will dismiss selection routine instead of looping
2016-04-18 21:07:34 -07:00
Aaron Culliney
399617e77f Avoid NullPointerException when choosing custom keypad joystick keys 2016-04-18 20:30:29 -07:00
Aaron Culliney
d416edc7d8 Change splash screen preferences button to a reset-preferences button
- Reset should be the only thing potentially needed from the splash screen (and even then hopefully not).  The main
      preferences menus can be accessed after starting emulation via back button or top right touch menu
2016-04-17 18:13:26 -07:00
Aaron Culliney
7607126f7c Avoid resetting joystick buttons too soon (to allow for ClosedApple-Reset to trigger) 2016-04-17 12:51:23 -07:00
Aaron Culliney
76f29d0865 Allow emulator shutdown state to be queried as needed 2016-04-17 12:18:48 -07:00
Aaron Culliney
3ed159dd0b Lightly refactor joystick and button handling during reset
- Remove unused button2 variable
    - Remake little-used backend joystick reset function as function pointer
    - Allow soft reset and self-test on Android (although there is still a race condition due to code that directly sets
      joy_button[12] values
2016-04-17 11:42:13 -07:00
Aaron Culliney
13184ec4be Change the button gesture recognizer state machine
- Use a synchronized event queue rather than overwriting "current" button state
    - Use aggressive assertions to ensure state machine is coherent
2016-04-16 14:16:34 -07:00
Aaron Culliney
9c9ba6e7ef Render the correct activeChar when initially setting touch joystick preferences 2016-04-16 14:15:21 -07:00
Aaron Culliney
dcb35226b7 Lightly refactor max CPU speed checks
- Better naming of the pivot point (@4.0x CPU)
    - Use floating point values divisible by 2 as the constants
2016-04-16 14:08:00 -07:00
Aaron Culliney
f53dbea81e Pause CPU on Linux desktop initialization 2016-04-16 13:33:50 -07:00
Aaron Culliney
65882152a8 Insure preferences are propagated regardless of enabled state 2016-04-13 21:23:37 -07:00
Aaron Culliney
04f22686fa Do not hide touchmenu for portrait calibration 2016-04-13 21:22:26 -07:00
Aaron Culliney
fd7578a862 Always show touch joystick azimuth (if we're rendering the joystick axis) 2016-04-13 19:50:03 -07:00
Aaron Culliney
23a874456c Better audio defaults 2016-04-13 19:45:55 -07:00
Aaron Culliney
2a405f028f Streamline and fix test codepaths on Desktop Linux and Android 2016-04-13 19:33:08 -07:00
Aaron Culliney
9c1cfedf7d Fix some envvars on Android 2016-04-12 22:16:01 -07:00
Aaron Culliney
db816ad502 Refactor Android variant to use common shared JSON preferences
- Eliminates a whole bunch of boilerplate setter/getter code in various places
    - Accesses preferences directly from JSON data through custom json_parse.h API
    - Improves code locality for Java menus/settings
2016-04-11 22:01:30 -07:00
Aaron Culliney
b59e1ca7ab json_parse module passes new tests 2016-03-29 22:39:59 -07:00
Aaron Culliney
a03c074a52 Moar JSON/prefs tests including new array tests 2016-03-29 22:38:28 -07:00
Aaron Culliney
b065da9f4b Various test suite improvements
- Possibly breaking changes for mobile ... #willunbreaklater
2016-03-26 14:56:34 -07:00
Aaron Culliney
74a5b74ae3 Refactor prefs into JSON publish/subscribe API
- Breaking changes currently only tested on Linux desktop build =P
    - Goal is to eventually eliminate most/many of the disparate getter/setter functions to allow better
      modularity/scaling and platform portability
2016-03-26 13:44:13 -07:00
Aaron Culliney
87ae0f08e0 Refactor JSMN for better conformance to JSON spec and to pass our more stringent tests 2016-03-25 22:32:53 -07:00
Aaron Culliney
163035b0c7 Add a bunch of new JSON tests 2016-03-25 20:44:03 -07:00
Aaron Culliney
8b74dafc6f Check for and disallow NULL tokens in one location 2016-03-22 22:40:59 -07:00
Aaron Culliney
2da23f717a Simplify and refactor JSMN
- Always enable strict mode
    - Always calculate parent links and skip counts
2016-03-22 22:02:30 -07:00
Aaron Culliney
fdfe312a2b Changes needed after jsmn subtree upgrade 2016-03-09 22:21:15 -08:00
Aaron Culliney
10d9cc3c66 Add APPLE2IX defines 2016-03-09 22:20:17 -08:00
Aaron Culliney
c57bf83514 Merge commit 'b6d20bbe3b2a92a6db810a4e3ef6d67e91a38d36' as 'externals/jsmn' 2016-03-09 21:09:57 -08:00
Aaron Culliney
b6d20bbe3b Squashed 'externals/jsmn/' content from commit 3459957
git-subtree-dir: externals/jsmn
git-subtree-split: 345995799311ed1e26ea20e3eeaa47224cd1a77d
2016-03-09 21:09:57 -08:00
Aaron Culliney
48c6515790 Remove inline import of JSMN 2016-03-09 21:07:05 -08:00
Aaron Culliney
a87092bbd8 Add JSON mutation functions and tests 2016-03-09 21:06:00 -08:00
Aaron Culliney
01b25527fe Add JSON serialization and tests
- Encourage use of opaque reference rather than internal struct
2016-03-04 23:31:15 -08:00
Aaron Culliney
b87273e742 Expand coverage of heap memory checking of debug builds and simplify FREE() calls 2016-02-26 21:26:57 -08:00
Aaron Culliney
0663141589 Misc tweaks 2016-02-25 21:04:23 -08:00
Aaron Culliney
6e978810db First cut at testsuite for json/prefs 2016-02-25 20:56:59 -08:00
Aaron Culliney
b5dfd86310 Changes insited upon by Android Studio 2016-02-23 00:10:41 -08:00
Aaron Culliney
3120b75e00 Enforce module ctor priorities 2016-02-22 22:41:37 -08:00
Aaron Culliney
c5ed0bb5c6 Switch Android toolchain to stable Clang 2016-02-20 13:48:00 -08:00
Aaron Culliney
44b9c4c408 Bump Android version to 1.1.7 2016-02-20 13:39:59 -08:00
Aaron Culliney
03930d6acb Add missing (nontranslated) strings to get release build "lint" check passing
- Sweeps the problem under the rug, but contributions from native speakers are always welcome =)
2016-02-20 11:59:57 -08:00
Aaron Culliney
15d64cf24e Render thread will actually eject the disks through emulator_shutdown() 2016-02-20 11:09:24 -08:00
Aaron Culliney
dcbf1ace18 Remove an assertion that can be wrong if user is spamming app lifecycle backgrounding/foregrounding
- Unlikely to be a real problem if CPU thread is not paused when we are initializing the render thread
2016-02-20 11:07:29 -08:00
Aaron Culliney
f0e55af6f0 Fix a glitch rerendering pressed ctrl key ... codegen error in ARM gcc 4.8 toolchain? 2016-02-19 21:41:34 -08:00
Aaron Culliney
699f4697df Don't leak azimuth model objects 2016-02-19 20:00:42 -08:00
Aaron Culliney
efcf0afa4e Set framebuffer dirty on restore state 2016-02-17 22:38:50 -08:00
Aaron Culliney
00a5f6e102 Fix "re-insert" disk edge case 2016-02-17 22:32:01 -08:00
Aaron Culliney
e073954ec8 Add a test case that shows a problem with "re-inserted" disks 2016-02-17 22:24:07 -08:00
Aaron Culliney
afeaba8b2a Lock access to zlib functions and unlinking image files to prevent data loss 2016-02-17 21:31:43 -08:00
Aaron Culliney
a2b7603e53 Only reset disk stepper_phases during VM initialization and allow for save/restoring it 2016-02-17 21:10:09 -08:00
Aaron Culliney
0dbcfbc9a2 Various fixes to get Mac and iOS building 2016-02-15 21:10:16 -08:00
Aaron Culliney
89d12ce29d Misc fixes for Android portrait/landscape mode settings 2016-02-15 14:10:29 -08:00
Aaron Culliney
fccd2629d4 Clear color should be solid and should be set by toplevel glnode 2016-02-15 14:03:52 -08:00
Aaron Culliney
bac745845b Don't allow reaching beyond buffer row bounds 2016-02-15 14:01:59 -08:00
Aaron Culliney
545376b1be Improve testcpu on desktop and fix it on Android 2016-02-15 13:32:05 -08:00
Aaron Culliney
7ef70e7943 Make use of the video memory dirty bit 2016-02-15 13:30:35 -08:00
Aaron Culliney
5ce91fa96d Do not test internal implementation detail that no longer exists 2016-02-15 13:30:31 -08:00
Aaron Culliney
829291feba Refactor all drawing to run on video thread in one scanner pass
- Moves extra work off CPU/audio thread
    - Prepares the way to possibly remove the vestigal intermediate indexed framebuffer and do direct RGBA_8888 writes
      to XImage or OpenGL texture
    - Prepares the ground for possible better emulation of video scanner hardware

Conflicts:
	src/display.c
	src/video/glvideo.c
	src/video/video.h
2016-02-15 13:29:58 -08:00
Aaron Culliney
bd9b38cd65 Allow calibration of portrait mode on Android 2016-02-14 20:40:51 -08:00
Aaron Culliney
5354b0cfd5 First cut at allowing portrait mode (on mobile devices) 2016-02-14 20:18:07 -08:00
Aaron Culliney
70afe71c82 Add various new JSON data format parsing functions 2016-02-13 21:19:06 -08:00
Aaron Culliney
5bb65e3fc3 Calculate and store skip-ahead counts 2016-02-13 21:18:54 -08:00
Aaron Culliney
261ae2efae Refactor to not use video_backend in so many places 2016-02-07 14:42:32 -08:00
Aaron Culliney
5fec80a33a Refactor video_shutdown() to appease Valgrind
- This likely breaks Darwin ports ... will fix soon[ish]
2016-02-07 14:42:28 -08:00
Aaron Culliney
edaae0bc89 Refactor glvideo to be the root glnode, begin to privatize video_backend APIs 2016-02-07 14:42:16 -08:00
Aaron Culliney
51fb905260 Upgrade Android Studio stuff-n-things 2016-02-07 13:20:15 -08:00
Aaron Culliney
8414dea54c Make warnings more explicit for desktop POSIX builds 2016-01-31 13:32:50 -08:00
Aaron Culliney
c0b7b8dd9b Simplify XEvent processing loop and fix non-MITSHM codepath 2016-01-31 13:21:38 -08:00
Aaron Culliney
e638c13d9d Lock access to INTERFACE_CLASSIC thread on desktop POSIX 2016-01-31 13:18:19 -08:00
Aaron Culliney
2c07a0fe94 Better check-and-abort for flex in Desktop POSIX build 2016-01-31 13:13:56 -08:00
Aaron Culliney
433f4baafa Eliminate unnecessary variable and draw axis/buttons for duration of tracking touch 2016-01-30 14:46:49 -08:00
Aaron Culliney
8f03d0ca6f Enforce pointer index tracking for touch lifecycle
- Fixes glitch where GLTouchMenu would capture a joystick event if the movement came into its portion of the screen
    - Prepares the way for allowing both keyboard and joystick access to same screen real-estate
2016-01-30 14:46:43 -08:00
Aaron Culliney
c29daf006e Refactor : naming RB -> Azimuth 2016-01-30 14:46:31 -08:00
Aaron Culliney
ac151ae9e9 Bugfix Mockingboard volume getting improperly reset 2016-01-30 14:46:08 -08:00
Aaron Culliney
c5298eb6f0 Mockingboard is now default enabled on Android ... temporarily force this upgrade in version 16 2016-01-30 14:46:03 -08:00
Aaron Culliney
1912428ed3 Properly account for mockingboard card enabled/disabled 2016-01-30 14:45:51 -08:00
Aaron Culliney
ba07bcc274 Avoid resetting Mockingboard state machine when all we need to do is reset the backend
- Also slightly disentangles audio initialization from reinitialization
2016-01-30 14:45:35 -08:00
Aaron Culliney
b5fbf29f21 Don't let FLASH text counter overflow 2016-01-24 11:03:50 -08:00
Aaron Culliney
661b4220ef Unbreak TOUCH_JOY_LOG() macro 2016-01-24 10:25:45 -08:00
Aaron Culliney
1358a52eee Add but disable CPU trace on Android 2016-01-24 09:47:57 -08:00
Aaron Culliney
b9acdc7d4c reset FBSHA after LORES80 iteration 2016-01-24 09:47:44 -08:00
Aaron Culliney
dfbccaeb7f Changes insisted upon by new AndroidStudio 2016-01-23 13:08:44 -08:00
Aaron Culliney
d44aad062a ARM bugfix: actually return the correct value when reading from slot I/O card
- Allows NSCT.dsk to boot on ARM architecture devices
2016-01-23 13:00:45 -08:00
Aaron Culliney
f8e4ba3551 FLASHing text is handled by CPU timing and not render loop 2016-01-20 23:17:39 -08:00
Aaron Culliney
175df18401 Don't use long word for cycles counter
- Lightly optimize a hot code path
    - Include debug code for testing counter overflow
2016-01-20 23:17:30 -08:00
Aaron Culliney
edfd5243ff First cut at LORES80 graphics
- This comes with the caveat that all video modes will need to be refactored to take into account proper timing
2016-01-20 23:16:42 -08:00
Aaron Culliney
090f5f4db4 Don't actually draw unless byte written to active page or page was swapped
- Avoids "flicker" in 80's apps that use double buffering
    - Possibly improves performance since we only take the render hit in page swap (or direct render to visible page)
2016-01-20 23:16:34 -08:00
Aaron Culliney
37e5143d73 Store disk images gzipped in repo ... we should ungzip in a "copy resources" phase in Mac/iOS target if we need them ungzipped 2016-01-20 23:15:39 -08:00
Aaron Culliney
a8e6f4fc92 Refactor : coalesced text/mixed drawing codepath 2016-01-16 22:05:28 -08:00
Aaron Culliney
8626215205 Silence a number of spurious compiler warnings
- Existing warnings are now a good proxy for areas of code smell ;-)
2016-01-10 12:09:04 -08:00
Aaron Culliney
1dc08f4a25 Silence compiler warning about asprintf() when running flex 2016-01-10 11:41:54 -08:00
Aaron Culliney
c6503021c2 Bump Android version to 1.1.6 2016-01-09 22:45:48 -08:00
Aaron Culliney
4eaa7137a0 Merge branch 'develop' ... latest stable Linux, Android, Mac, iOS work 2016-01-09 14:26:14 -08:00
Aaron Culliney
e7eb467f4f Get Linux desktop build unstuck 2016-01-09 14:01:00 -08:00
Aaron Culliney
0c77e4a659 Fix testdisk assertions in debug builds 2016-01-09 14:00:33 -08:00
Aaron Culliney
e1adc07db0 Update README.md 2016-01-09 13:43:08 -08:00
Aaron Culliney
144086c667 Don't LOG() in Breakpad callback 2016-01-09 12:44:11 -08:00
Aaron Culliney
b2824e9145 Improve tap responsiveness in GL touch joystick 2016-01-08 00:28:57 -08:00
Aaron Culliney
a64941694f Expose timespec_add 2016-01-07 22:42:04 -08:00
Aaron Culliney
9bbfca3193 Take into account device screen small axis when calculating button switch threshold 2016-01-07 22:41:50 -08:00
Aaron Culliney
18c02d3673 Fix broken assert 2016-01-07 20:57:22 -08:00
Aaron Culliney
659a338d26 Avoid crashing if azimuth model is unavailable (shader failed to compile) 2016-01-05 23:19:12 -08:00
Aaron Culliney
4436b9b7ca Fix semi-opaque glyph shading with scales > 2
- Also add defensive coding in debug builds
2016-01-05 23:04:27 -08:00
Aaron Culliney
3089b98a30 For now, enable logging in release builds on Android 2016-01-05 21:39:04 -08:00
Aaron Culliney
94882c48bc Send app version in crash report 2016-01-05 20:58:57 -08:00
Aaron Culliney
f7cb3da434 Mark certain core emulation modules as problematic on iOS/Mac, and something we should fix 2016-01-05 00:11:58 -08:00
Aaron Culliney
281fccb73b Update misc project settings =) 2016-01-05 00:11:38 -08:00
Aaron Culliney
086f7d0da2 Do not free the model positions prematurely ... since we occasionally change them 2016-01-05 00:11:16 -08:00
Aaron Culliney
c5256dde85 Add new shader to iOS and Mac bundle 2016-01-05 00:10:11 -08:00
jvernet
1b2c6fff83 Project update 2016-01-04 21:25:28 -08:00
jvernet
5f21ef14a4 Macs Flat icons, Toolbar 2016-01-04 21:25:28 -08:00
jvernet
4de4fa6685 ?? 2016-01-04 21:25:28 -08:00
jvernet
680fbea853 Add missing IOS icon 2016-01-04 21:25:28 -08:00
jvernet
29e6dcde2f dsk document definition
so that you can download .dsk directly in Apple2IOS Document/inbox
directly from Safari. But need a way to show Inbox folder in Disk View…
2016-01-04 21:25:28 -08:00
jvernet
0fee86bc3c Added ReadOnly Switchs for disks 2016-01-04 21:25:27 -08:00
jvernet
b6a9f667ef Latest commit from mauiaaron need common.h in imagutil for FREE and MALLOC macros 2016-01-04 21:25:27 -08:00
jvernet
7008d21fcf IOS icon 2016-01-04 21:25:27 -08:00
jvernet
f05ce4d247 IOS icons 2016-01-04 21:25:27 -08:00
jvernet
6445546ff9 Toolbar Icons 2016-01-04 21:25:27 -08:00
jvernet
672d824946 Yes, it work !
https://youtu.be/C5no96qnCNQ
2016-01-04 21:25:27 -08:00
jvernet
4d89d173f4 Here we are: now everything work for iPad. 2016-01-04 21:25:27 -08:00
jvernet
86064cf1c6 Hard to understand those Segues ***ing 2016-01-04 21:25:27 -08:00
jvernet
b60a697b86 Finally ! First time loading disk !!! 2016-01-04 21:25:27 -08:00
jvernet
3cea55cc40 Est ce que je vais terminer un jour ? 2016-01-04 21:25:26 -08:00
jvernet
f722725992 IOS Pref more 2016-01-04 21:25:26 -08:00
jvernet
28875b5e76 IOS Prefs 2016-01-04 21:25:26 -08:00
jvernet
a3040dfb05 IOS stuff 2016-01-04 21:25:26 -08:00
jvernet
d23b0593fb IOS Stuff 2016-01-04 21:25:26 -08:00
jvernet
97700bbff6 More IOS work
but it doesn’t work better.
2016-01-04 21:25:26 -08:00
jvernet
b9a6f0060d test on real iPad 2016-01-04 21:25:26 -08:00
jvernet
4522fa98ac ToolBar properly at bottom 2016-01-04 21:25:26 -08:00
jvernet
bf341731f0 Separate View/controller 2016-01-04 21:25:26 -08:00
jvernet
b04e7760c2 IOS ToolBar Trying
Need to know how it work…
2016-01-04 21:25:26 -08:00
jvernet
f984bfc5b0 IOS 2016-01-04 21:25:25 -08:00
jvernet
b251e122e2 Localization Preferences 2016-01-04 21:25:25 -08:00
jvernet
4d1224ce29 Localization French 2016-01-04 21:25:25 -08:00
jvernet
746dcb2e3d Suite 2016-01-04 21:25:25 -08:00
jvernet
5d86735a69 Fixed Crash on reboot
It crash ? Killing // ;)
2016-01-04 21:25:25 -08:00
jvernet
f4258ade45 Initials changes
Added NSToolbar and French Localization
2016-01-04 21:25:25 -08:00
Aaron Culliney
40129d9a14 Bump Android version to 1.1.5 2016-01-04 20:41:46 -08:00
Aaron Culliney
e7e6af1335 WIP : first cut at MainMenu popup 2016-01-04 20:34:27 -08:00
Aaron Culliney
97f52ef2ff Get Android build unstuck 2016-01-04 20:34:27 -08:00
Aaron Culliney
5737e89150 Initial iOS target compiles, links, and appears to run in simulator and on device =) 2016-01-04 20:34:27 -08:00
Aaron Culliney
6af1309e2d Various project changes to prepare for iOS target 2016-01-04 20:33:35 -08:00
Aaron Culliney
042edd8bab Segregate Mac-specific classes into OSX group 2016-01-04 20:33:35 -08:00
Aaron Culliney
0b7f3a75b5 deal with short writes 2016-01-04 20:33:35 -08:00
Aaron Culliney
0bb0df0960 Support building Android version in release mode (without -g and with -DNDEBUG=1) 2016-01-03 13:59:52 -08:00
Aaron Culliney
64338d30c3 German translation from Guido Lehwalder 2016-01-03 13:50:55 -08:00
Aaron Culliney
bda8efeb45 Slight tweak to custom keyboard 2016-01-03 13:09:57 -08:00
Aaron Culliney
5a2529651c Fix recent Android build break 2016-01-03 13:09:23 -08:00
Aaron Culliney
08a6d99334 Fix assert/crash on desktop Linux introduced by overzealous malloc fencing 2016-01-03 13:01:48 -08:00
Aaron Culliney
c89ee87bd4 Tweak heuristic calculation of when to use 2x glyphs 2016-01-03 12:58:12 -08:00
Aaron Culliney
d8fc04ec2e Expose ability to set azimuth visibility on Android 2016-01-03 12:49:23 -08:00
Aaron Culliney
42feeb1bd3 Remove new English strings
- Native speakers are encouraged to send me a pull req for these ;-)
2016-01-03 12:48:01 -08:00
Aaron Culliney
c2014e2cde Add simple display of touch joystick azimuth 2016-01-03 12:36:13 -08:00
Aaron Culliney
29282dcac6 Expose identity matrix and only calculate it once 2016-01-03 12:02:06 -08:00
Aaron Culliney
9219fa7053 Creation of GLCustom model is slightly more RAII 2016-01-03 11:57:48 -08:00
Aaron Culliney
2ec88ad433 Iterate on GLTouchJoystick
- Use smaller models/textures
    - Slightly change the joystick/kpad glyphs
2016-01-02 15:08:06 -08:00
Aaron Culliney
691dbd5ac2 Coalesce duplicate fade-out codepaths 2016-01-02 14:23:09 -08:00
Aaron Culliney
8b3f288018 Reduce duplicate OpenGL plumbing codepaths
- Apple //e "framebuffer" OpenGL model/texture is now a complete GLModel object
    - Readability FTW! ... use args struct in quadCreation function to allow for named args
2016-01-02 13:39:07 -08:00
Aaron Culliney
7ca679350d Move shader/program creation into video_util module 2016-01-02 11:33:47 -08:00
Aaron Culliney
b59672815f Allow setting keyboard and HUD menu glyph scale 2016-01-02 00:14:21 -08:00
Aaron Culliney
904d89aff8 Get touch menu min/max alpha matching touch keyboard 2016-01-01 21:38:35 -08:00
Aaron Culliney
39c654ae9d Simplify and coalesce touch menu settings into keyboard settings for now 2016-01-01 21:35:49 -08:00
Aaron Culliney
b1cbe44a05 Placeholder menu strings 2016-01-01 19:58:46 -08:00
Aaron Culliney
f5bbda4c6e Refactor HUD model to allow scaling font glyphs
- Should be less pixelation of touch keyboard on large tablet screens
2016-01-01 15:02:29 -08:00
Aaron Culliney
a54a69efcc Call "super init" during HUD model creation 2015-12-31 22:54:34 -08:00
Aaron Culliney
abae59767c Enable simple heap memory fencing/scribbling in debug builds 2015-12-31 22:50:43 -08:00
Aaron Culliney
fce1f010b0 Bump Android version to 1.1.4 2015-12-28 19:01:34 -08:00
Aaron Culliney
20278ca91e Migration to 1.1.3+ should remove disks that were excised 2015-12-24 22:17:29 -08:00
Aaron Culliney
b996083a42 tweaks for Spanish localizations 2015-12-24 21:12:24 -08:00
Aaron Culliney
3e777477e2 Wish profuse apologies to native speakers, first cut at Spanish translation 2015-12-22 21:54:18 -08:00
Aaron Culliney
4d9eda4862 Work around another UI race exposed by NVIDIA Shield Portable 2015-12-22 21:53:14 -08:00
Aaron Culliney
4deedb2215 Move main menu dialog code into Apple2MainMenu 2015-12-20 16:45:59 -08:00
Aaron Culliney
bf3ef3ae18 GZIP the supplied disk images to help cut down on app space 2015-12-20 15:18:09 -08:00
Aaron Culliney
9fd288c0b9 Remove aggressive assert that fired on Cyanogen device (even with a valid CPU pause/resume lifecycle) ... 2015-12-20 15:04:20 -08:00
Aaron Culliney
abe1346051 Remove unused method 2015-12-20 15:04:06 -08:00
Aaron Culliney
145b6a6cef Refactor JNI plumbing for consistency 2015-12-20 12:54:27 -08:00
Aaron Culliney
22b1bc9ad8 Move key handling back to the Activity, otherwise we don't seem to receive onKeyUp() for the Android back button 2015-12-20 12:11:12 -08:00
Aaron Culliney
2f7f7bd359 Preliminary support for mapping an Android physical gamepad/joystick to emulated Apple //e joystick
- No support for custom joystick/gamepad configurations (yet)
2015-12-19 23:59:02 -08:00
Aaron Culliney
7dd0f0b3cf Avoid another UI race exposed by NVIDIA Shield Portable
- Use AtomicBoolean to avoid spurious extra taps/clicks/presses
2015-12-19 23:24:45 -08:00
Aaron Culliney
3ff877d80c Refactor : move event handling into custom view 2015-12-19 23:13:52 -08:00
Aaron Culliney
13de08f8cb map L1 and R1 keys to joy buttons 2015-12-19 23:13:44 -08:00
Aaron Culliney
1d248c5e22 Whitespace and style changes 2015-12-19 23:13:33 -08:00
Aaron Culliney
e5e2f98835 Avoid nasty UI race manifesting on NVIDIA Shield Portable
- For some reason this device has really slow Java UI, so it exposed a case where spammy touch resulted in two
      save/restore popups and a subsequent assert on the native side if you saved while the emulation was resumed.
2015-12-19 23:12:05 -08:00
Aaron Culliney
1e573c34ad Meager support for Android joystick/gamepad keys 2015-12-19 13:14:46 -08:00
Aaron Culliney
e165da72fc Explicitly not supporting older joystick devices 2015-12-19 13:14:10 -08:00
Aaron Culliney
1c50d12963 Source InputManager from AOSP 2015-12-19 13:08:48 -08:00
Aaron Culliney
d69b416c58 Lightly refactor emulator joystick API in preparation for more comprehensive refactor 2015-12-19 13:01:28 -08:00
Aaron Culliney
a1fbc63f98 Semi-inscrutible changes insisted upon by Android Studio 2015-12-19 12:59:40 -08:00
Aaron Culliney
c7c209907b Allow for keyboard to completely own/cover screen
- Includes updated alternate/custom keyboards
2015-12-19 12:56:24 -08:00
Aaron Culliney
5bd7ff2fe1 Simplify switching to "useralt" touch keyboard 2015-12-16 22:15:19 -08:00
Aaron Culliney
72fb577166 Update misc Android assets 2015-12-16 22:14:29 -08:00
Aaron Culliney
69b096ec14 Avoid showing redundant 0 nibble 2015-12-16 21:33:25 -08:00
Aaron Culliney
f872e0c05c fix i386 variant of op_SBC_dec (now re-passes tests on x86 Android) 2015-12-16 21:12:45 -08:00
Aaron Culliney
5211722d63 Switch to using DSK to speed up tests on Android 2015-12-16 21:11:52 -08:00
Aaron Culliney
785577e252 Fix some compiler warnings in testdisk.c 2015-12-16 21:11:35 -08:00
Aaron Culliney
3a20c96296 Check more paths in attempt to satisfy test suite disk insertion
- This is mainly to accommodate disk locations on Android
2015-12-16 21:01:50 -08:00
Aaron Culliney
2c8284d41f Start running tests on Android after first resume
- This allows for a more consistent test environment (test disks copied out of APK and available, etc) and also
      mimics main app behavior
2015-12-16 20:59:20 -08:00
Aaron Culliney
9e5274ee18 Flip-flop test to correctly account for e5f08849 2015-12-16 20:56:20 -08:00
Aaron Culliney
26e452c3a3 Avoid bufover in testsuite -- sprintf() already writes terminating null byte! 2015-12-16 20:53:16 -08:00
Aaron Culliney
870a24ced6 Fix test compilation on Android 2015-12-15 22:24:43 -08:00
Aaron Culliney
2dbd77e344 Excise uthash.h from testcpu suite
- Works around issue where Android NDK GCC 4.8/4.9 would take an inordinate amount of time when compiling testcpu with -O2
2015-12-14 23:56:48 -08:00
Aaron Culliney
699746a743 Alleviate compiler warnings in testvm/testdisplay 2015-12-14 23:55:08 -08:00
Aaron Culliney
74e97ccc97 Alleviate compiler warnings in testcpu 2015-12-14 22:11:29 -08:00
Aaron Culliney
1f0021667b Fix display test failures
- New framebuffer SHAs result of recent improvements to DHIRES colors
2015-12-14 18:59:44 -08:00
Aaron Culliney
836044c80b Bump Android version to 1.1.3 2015-12-13 22:48:29 -08:00
Aaron Culliney
b445c2dcf1 Merge branch 'develop' ... latest Linux, Android, and Mac variant 2015-11-14 11:10:11 -08:00
Aaron Culliney
9955abb9e7 Pick in docs images on develop branch 2015-04-12 16:16:25 -07:00
1271 changed files with 114932 additions and 28141 deletions

11
.apple2
View File

@@ -1,11 +0,0 @@
speed = 1.00
altspeed = 4.00
disk path = /usr/local/games/apple2/disks
color = interpolated
video = 1X
volume = 8
caps lock = 1
joystick = joy keypad
system path = /usr/local/games/apple2/rom
pc joystick parms = 128 128 255 1 255 1
keypad joystick parms = 8 1

4
.gitignore vendored
View File

@@ -51,9 +51,6 @@ src/rom.c
src/x86/glue.S
src/arm/glue.S
# sub{tree,module}
src/rom
# generated binaries
/apple2ix
genfont
@@ -82,6 +79,7 @@ Android/local.properties
Android/.idea/workspace.xml
Android/.idea/libraries
Android/.idea/dictionaries
Android/.idea/caches
Android/build
Android/jni/obj
Android/obj

14
ASM
View File

@@ -1,14 +0,0 @@
Begin3
Title: Apple //ix
Author: alexb@csd.uu.se (Alexander Jean-Claude Bottema)
sl14@cornell.edu (Stephen Lee)
michael@talamasca.ocis.net (Michael Deutschmann)
ASC _at_ BITR0T (Aaron Culliney)
Version: 0.8
Entered-date: 2014-02-27
Description: Apple //e emulator for POSIX systems
Keywords: emulator, linux, posix
Uploader: ASC _at_ BITR0T (Aaron Culliney)
Primary-site: https://github.com/mauiaaron/apple2
Platform: Linux i386
End

1
Android/.idea/.name generated
View File

@@ -1 +0,0 @@
Android

116
Android/.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,116 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@@ -1,9 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright (c) deadc0de.org" />
<option name="keyword" value="Copyright" />
<option name="allowReplaceKeyword" value="" />
<option name="myName" value="deadc0de.org" />
<option name="myLocal" value="true" />
</copyright>
</component>

View File

@@ -1,3 +0,0 @@
<component name="CopyrightManager">
<settings default="" />
</component>

4
Android/.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

View File

@@ -3,16 +3,18 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT" />
</compositeConfiguration>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.4" />
<option name="gradleJvm" value="1.7" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>

34
Android/.idea/misc.xml generated
View File

@@ -1,43 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<list size="10">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<list size="9">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
</list>
</value>
</option>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

2
Android/.idea/vcs.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="" />
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="Android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<module external.linked.project.id="Android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>

View File

@@ -1 +0,0 @@
app/build/intermediates/manifests/full/debug/AndroidManifest.xml

View File

@@ -1,97 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="Android" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" value="3.5.2" />
<option name="LAST_KNOWN_AGP_VERSION" value="3.5.2" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<afterSyncTasks>
<task>generateDebugAndroidTestSources</task>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res;file://$MODULE_DIR$/build/generated/res/resValues/debug" />
<option name="TEST_RES_FOLDERS_RELATIVE_PATH" value="" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
<output url="file://$MODULE_DIR$/build/intermediates/javac/debug/classes" />
<output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/classes" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/ap_generated_sources/debug/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debug/compileDebugRenderscript/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/ap_generated_sources/debugAndroidTest/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debugAndroidTest/compileDebugAndroidTestAidl/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debugAndroidTest/compileDebugAndroidTestRenderscript/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/ap_generated_sources/debugUnitTest/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.1.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.1.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android API 29 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="support-annotations-23.1.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.1.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.1.0" level="project" />
<orderEntry type="library" name="Gradle: androidx.collection:collection:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-common:2.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: androidx.arch.core:core-common:2.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: androidx.annotation:annotation:1.1.0@jar" level="project" />
<orderEntry type="library" name="Gradle: androidx.appcompat:appcompat:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.fragment:fragment:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.appcompat:appcompat-resources:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.drawerlayout:drawerlayout:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.viewpager:viewpager:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.loader:loader:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.activity:activity:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.vectordrawable:vectordrawable-animated:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.vectordrawable:vectordrawable:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.customview:customview:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.core:core:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.cursoradapter:cursoradapter:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.versionedparcelable:versionedparcelable:1.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-viewmodel:2.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-runtime:2.1.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.savedstate:savedstate:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-livedata:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-livedata-core:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.interpolator:interpolator:1.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: androidx.arch.core:core-runtime:2.0.0@aar" level="project" />
</component>
</module>

View File

@@ -1,8 +1,7 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "21.1.2"
compileSdkVersion 29
signingConfigs {
release {
storeFile file("release2.keystore")
@@ -22,13 +21,33 @@ android {
jniDebuggable true
}
}
splits {
// Configures multiple APKs based on ABI
abi {
// Enables building multiple APKs per ABI
enable true
// Resets the list of ABIs that Gradle should create APKs for to none
reset()
// Include just these ...
include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
// Specifies that we do not want to also generate a universal APK that includes all ABIs
//universalApk false
}
}
lintOptions {
checkReleaseBuilds false
}
defaultConfig {
applicationId "org.deadc0de.apple2ix.basic"
minSdkVersion 10
targetSdkVersion 23
versionCode 12
versionName "1.1.2"
minSdkVersion 14
targetSdkVersion 29
versionCode 26
versionName "2.1.0-RC3"
ndk {
moduleName "apple2ix"
}
@@ -36,6 +55,6 @@ android {
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.1.0'
api fileTree(dir: 'libs', include: ['*.jar'])
api 'androidx.appcompat:appcompat:1.1.0'
}

View File

@@ -3,6 +3,7 @@
package="org.deadc0de.apple2ix.basic" >
<uses-feature android:glEsVersion="0x00020000" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<supports-screens
@@ -12,7 +13,9 @@
android:smallScreens="false" />
<application
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:fullBackupContent="@xml/a2backupscheme"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
@@ -20,8 +23,9 @@
android:name="org.deadc0de.apple2ix.Apple2Activity"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation"
android:screenOrientation="sensorLandscape"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:windowSoftInputMode="adjustResize" >
android:theme="@style/Theme.AppCompat.NoActionBar.FullScreen"
android:launchMode="singleTask"
android:windowSoftInputMode="stateHidden|adjustNothing" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -29,26 +33,30 @@
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" android:host="*" android:mimeType="application/x-gzip" android:pathPattern="/.*\\.nib\\.gz" />
<data android:scheme="file" android:host="*" android:mimeType="application/x-gzip" android:pathPattern="/.*\\.dsk\\.gz" />
<data android:scheme="file" android:host="*" android:mimeType="application/x-gzip" android:pathPattern="/.*\\.do\\.gz" />
<data android:scheme="file" android:host="*" android:mimeType="application/x-gzip" android:pathPattern="/.*\\.po\\.gz" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" /> <!-- catch-all since I can't get the following to work because ... Android -->
<!--
<data android:scheme="file" android:host="*" android:mimeType="application/octet-stream" android:pathPattern="/.*\\.nib" />
<data android:scheme="file" android:host="*" android:mimeType="application/octet-stream" android:pathPattern="/.*\\.dsk" />
<data android:scheme="file" android:host="*" android:mimeType="application/octet-stream" android:pathPattern="/.*\\.do" />
<data android:scheme="file" android:host="*" android:mimeType="application/octet-stream" android:pathPattern="/.*\\.po" />
-->
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="apple2ix" />
</intent-filter>
</activity>
<activity android:name="org.deadc0de.apple2ix.Apple2DiskChooserActivity"
android:theme="@style/Theme.AppCompat"
/>
<activity android:name="org.deadc0de.apple2ix.Apple2EmailerActivity"
android:theme="@style/Theme.AppCompat"
android:screenOrientation="portrait"
/>
<provider
android:authorities="${applicationId}.provider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,73 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*/
package android.widget;
import android.content.Context;
import android.graphics.Canvas;
import androidx.appcompat.widget.AppCompatSeekBar;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class VerticalSeekBar extends AppCompatSeekBar {
public VerticalSeekBar(Context context) {
super(context);
}
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public VerticalSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
protected void onDraw(Canvas c) {
c.rotate(-90);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
int i = 0;
i = getMax() - (int) (getMax() * event.getY() / getHeight());
setProgress(i);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
@Override
public synchronized void setProgress(int progress) {
super.setProgress(progress);
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.inputmanagercompat;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.view.InputDevice;
import android.view.MotionEvent;
public interface InputManagerCompat {
/**
* Gets information about the input device with the specified id.
*
* @param id The device id
* @return The input device or null if not found
*/
public InputDevice getInputDevice(int id);
/**
* Gets the ids of all input devices in the system.
*
* @return The input device ids.
*/
public int[] getInputDeviceIds();
/**
* Registers an input device listener to receive notifications about when
* input devices are added, removed or changed.
*
* @param listener The listener to register.
* @param handler The handler on which the listener should be invoked, or
* null if the listener should be invoked on the calling thread's
* looper.
*/
public void registerInputDeviceListener(InputManagerCompat.InputDeviceListener listener,
Handler handler);
/**
* Unregisters an input device listener.
*
* @param listener The listener to unregister.
*/
public void unregisterInputDeviceListener(InputManagerCompat.InputDeviceListener listener);
/*
* The following three calls are to simulate V16 behavior on pre-Jellybean
* devices. If you don't call them, your callback will never be called
* pre-API 16.
*/
/**
* Pass the motion events to the InputManagerCompat. This is used to
* optimize for polling for controllers. If you do not pass these events in,
* polling will cause regular object creation.
*
* @param event the motion event from the app
*/
public void onGenericMotionEvent(MotionEvent event);
/**
* Tell the V9 input manager that it should stop polling for disconnected
* devices. You can call this during onPause in your activity, although you
* might want to call it whenever your game is not active (or whenever you
* don't care about being notified of new input devices)
*/
public void onPause();
/**
* Tell the V9 input manager that it should start polling for disconnected
* devices. You can call this during onResume in your activity, although you
* might want to call it less often (only when the gameplay is actually
* active)
*/
public void onResume();
public interface InputDeviceListener {
/**
* Called whenever the input manager detects that a device has been
* added. This will only be called in the V9 version when a motion event
* is detected.
*
* @param deviceId The id of the input device that was added.
*/
void onInputDeviceAdded(int deviceId);
/**
* Called whenever the properties of an input device have changed since
* they were last queried. This will not be called for the V9 version of
* the API.
*
* @param deviceId The id of the input device that changed.
*/
void onInputDeviceChanged(int deviceId);
/**
* Called whenever the input manager detects that a device has been
* removed. For the V9 version, this can take some time depending on the
* poll rate.
*
* @param deviceId The id of the input device that was removed.
*/
void onInputDeviceRemoved(int deviceId);
}
/**
* Use this to construct a compatible InputManager.
*/
public static class Factory {
/**
* Constructs and returns a compatible InputManger
*
* @param context the Context that will be used to get the system
* service from
* @return a compatible implementation of InputManager
*/
public static InputManagerCompat getInputManager(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return new InputManagerV16(context);
} else {
return null;
}
}
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.inputmanagercompat;
import android.annotation.TargetApi;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Build;
import android.os.Handler;
import android.view.InputDevice;
import android.view.MotionEvent;
import java.util.HashMap;
import java.util.Map;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class InputManagerV16 implements InputManagerCompat {
private final InputManager mInputManager;
private final Map<InputManagerCompat.InputDeviceListener, V16InputDeviceListener> mListeners;
public InputManagerV16(Context context) {
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
mListeners = new HashMap<InputManagerCompat.InputDeviceListener, V16InputDeviceListener>();
}
@Override
public InputDevice getInputDevice(int id) {
return mInputManager.getInputDevice(id);
}
@Override
public int[] getInputDeviceIds() {
return mInputManager.getInputDeviceIds();
}
static class V16InputDeviceListener implements InputManager.InputDeviceListener {
final InputManagerCompat.InputDeviceListener mIDL;
public V16InputDeviceListener(InputDeviceListener idl) {
mIDL = idl;
}
@Override
public void onInputDeviceAdded(int deviceId) {
mIDL.onInputDeviceAdded(deviceId);
}
@Override
public void onInputDeviceChanged(int deviceId) {
mIDL.onInputDeviceChanged(deviceId);
}
@Override
public void onInputDeviceRemoved(int deviceId) {
mIDL.onInputDeviceRemoved(deviceId);
}
}
@Override
public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
V16InputDeviceListener v16Listener = new V16InputDeviceListener(listener);
mInputManager.registerInputDeviceListener(v16Listener, handler);
mListeners.put(listener, v16Listener);
}
@Override
public void unregisterInputDeviceListener(InputDeviceListener listener) {
V16InputDeviceListener curListener = mListeners.remove(listener);
if (null != curListener)
{
mInputManager.unregisterInputDeviceListener(curListener);
}
}
@Override
public void onGenericMotionEvent(MotionEvent event) {
// unused in V16
}
@Override
public void onPause() {
// unused in V16
}
@Override
public void onResume() {
// unused in V16
}
}

View File

@@ -11,10 +11,12 @@
package org.deadc0de.apple2ix;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatCheckBox;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -82,6 +84,12 @@ public abstract class Apple2AbstractMenu implements Apple2MenuView {
public String getSummary(final Apple2Activity activity);
public String getPrefDomain();
public String getPrefKey();
public Object getPrefDefault();
public View getView(final Apple2Activity activity, View convertView);
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked);
@@ -213,7 +221,7 @@ public abstract class Apple2AbstractMenu implements Apple2MenuView {
}
protected static CheckBox _addCheckbox(Apple2Activity activity, IMenuEnum setting, View convertView, boolean isChecked) {
CheckBox checkBox = new CheckBox(activity);
AppCompatCheckBox checkBox = new AppCompatCheckBox(activity);
checkBox.setChecked(isChecked);
LinearLayout layout = (LinearLayout) convertView.findViewById(R.id.a2preference_widget_frame);
layout.addView(checkBox);

View File

@@ -12,44 +12,43 @@
package org.deadc0de.apple2ix;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.net.Uri;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.StrictMode;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;
import org.deadc0de.apple2ix.basic.BuildConfig;
import org.deadc0de.apple2ix.basic.R;
import org.json.JSONException;
import org.json.JSONObject;
public class Apple2Activity extends Activity {
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
public class Apple2Activity extends AppCompatActivity implements Apple2DiskChooserActivity.Callback, Apple2EmailerActivity.Callback
{
private final static String TAG = "Apple2Activity";
private final static int MAX_FINGERS = 32;// HACK ...
private final static String SAVE_FILE = "emulator.state";
private static volatile boolean DEBUG_STRICT = false;
private Apple2View mView = null;
private Runnable mGraphicsInitializedRunnable = null;
private Apple2SplashScreen mSplashScreen = null;
private Apple2MainMenu mMainMenu = null;
private Apple2SettingsMenu mSettingsMenu = null;
@@ -59,9 +58,9 @@ public class Apple2Activity extends Activity {
private ArrayList<AlertDialog> mAlertDialogs = new ArrayList<AlertDialog>();
private AtomicBoolean mPausing = new AtomicBoolean(false);
private AtomicBoolean mSwitchingToPortrait = new AtomicBoolean(false);
private float[] mXCoords = new float[MAX_FINGERS];
private float[] mYCoords = new float[MAX_FINGERS];
private static DiskArgs sDisksChosen = null;
// non-null if we failed to load/link the native code ... likely we are running on some bizarre 'droid variant
private static Throwable sNativeBarfedThrowable = null;
@@ -76,53 +75,37 @@ public class Apple2Activity extends Activity {
}
}
public final static long NATIVE_TOUCH_HANDLED = (1 << 0);
public final static long NATIVE_TOUCH_REQUEST_SHOW_MENU = (1 << 1);
public final static long NATIVE_TOUCH_KEY_TAP = (1 << 4);
public final static long NATIVE_TOUCH_KBD = (1 << 5);
public final static long NATIVE_TOUCH_JOY = (1 << 6);
public final static long NATIVE_TOUCH_MENU = (1 << 7);
public final static long NATIVE_TOUCH_JOY_KPAD = (1 << 8);
public final static long NATIVE_TOUCH_INPUT_DEVICE_CHANGED = (1 << 16);
public final static long NATIVE_TOUCH_CPU_SPEED_DEC = (1 << 17);
public final static long NATIVE_TOUCH_CPU_SPEED_INC = (1 << 18);
public final static long NATIVE_TOUCH_ASCII_SCANCODE_SHIFT = 32;
public final static long NATIVE_TOUCH_ASCII_SCANCODE_MASK = 0xFFFFL;
public final static long NATIVE_TOUCH_ASCII_MASK = 0xFF00L;
public final static long NATIVE_TOUCH_SCANCODE_MASK = 0x00FFL;
public final static int REQUEST_PERMISSION_RWSTORE = 42;
private native void nativeOnCreate(String dataDir, int sampleRate, int monoBufferSize, int stereoBufferSize);
private native void nativeOnKeyDown(int keyCode, int metaState);
private static native void nativeOnCreate(String dataDir, int sampleRate, int monoBufferSize, int stereoBufferSize);
private native void nativeOnKeyUp(int keyCode, int metaState);
private static native void nativeOnKeyDown(int keyCode, int metaState);
private native void nativeSaveState(String path);
private static native void nativeOnKeyUp(int keyCode, int metaState);
private native String nativeLoadState(String path);
private static native void nativeSaveState(String saveStateJson);
public native void nativeEmulationResume();
private static native String nativeStateExtractDiskPaths(String extractStateJson);
public native void nativeEmulationPause();
private static native String nativeLoadState(String loadStateJson);
public native void nativeOnQuit();
private static native boolean nativeEmulationResume();
public native long nativeOnTouch(int action, int pointerCount, int pointerIndex, float[] xCoords, float[] yCoords);
private static native boolean nativeEmulationPause();
public native void nativeReboot();
private static native void nativeOnQuit();
public native void nativeChooseDisk(String path, boolean driveA, boolean readOnly);
private static native void nativeReboot(int resetState);
public native void nativeEjectDisk(boolean driveA);
private static native void nativeLogMessage(String jsonStr);
public final static boolean isNativeBarfed() {
return sNativeBarfed;
}
@Override
public void onCreate(Bundle savedInstanceState) {
protected void onCreate(Bundle savedInstanceState) {
if (Apple2Activity.DEBUG_STRICT && BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
@@ -139,7 +122,7 @@ public class Apple2Activity extends Activity {
}
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate()");
logMessage(LogType.ERROR, TAG, "onCreate()");
// placeholder view on initial launch
if (mView == null) {
@@ -148,41 +131,46 @@ public class Apple2Activity extends Activity {
Apple2CrashHandler.getInstance().initializeAndSetCustomExceptionHandler(this);
if (sNativeBarfed) {
Log.e(TAG, "NATIVE BARFED...", sNativeBarfedThrowable);
logMessage(LogType.ERROR, TAG, "NATIVE BARFED : " + sNativeBarfedThrowable.getMessage());
return;
}
int sampleRate = DevicePropertyCalculator.getRecommendedSampleRate(this);
int monoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(this, /*isStereo:*/false);
int stereoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(this, /*isStereo:*/true);
Log.d(TAG, "Device sampleRate:" + sampleRate + " mono bufferSize:" + monoBufferSize + " stereo bufferSize:" + stereoBufferSize);
logMessage(LogType.DEBUG, TAG, "Device sampleRate:" + sampleRate + " mono bufferSize:" + monoBufferSize + " stereo bufferSize:" + stereoBufferSize);
String dataDir = Apple2DisksMenu.getDataDir(this);
String dataDir = Apple2Utils.getDataDir(this);
nativeOnCreate(dataDir, sampleRate, monoBufferSize, stereoBufferSize);
final boolean firstTime = (Apple2Preferences.EMULATOR_VERSION.intValue(this) != BuildConfig.VERSION_CODE);
if (firstTime) {
// allow for primitive migrations as needed
Apple2Preferences.EMULATOR_VERSION.saveInt(this, BuildConfig.VERSION_CODE);
Log.v(TAG, "Triggering migration to Apple2ix version : "+BuildConfig.VERSION_NAME);
}
// NOTE: ordering here is important!
Apple2Preferences.load(this);
final boolean firstTime = Apple2Preferences.migrate(this);
mSwitchingToPortrait.set(false);
boolean switchingToPortrait = Apple2VideoSettingsMenu.SETTINGS.applyLandscapeMode(this);
Apple2Preferences.sync(this, null);
Apple2DisksMenu.insertDisk(this, new DiskArgs((String) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A)), /*driveA:*/true, /*isReadOnly:*/(boolean) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A_RO), /*onLaunch:*/true);
Apple2DisksMenu.insertDisk(this, new DiskArgs((String) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B)), /*driveA:*/false, /*isReadOnly:*/(boolean) Apple2Preferences.getJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B_RO), /*onLaunch:*/true);
showSplashScreen(!firstTime);
// Is there a way to persist the user orientation setting such that we launch in the previously set orientation and avoid getting multiple onCreate() onResume()?! ... Android lifecycle edge cases are so damn kludgishly annoying ...
mSwitchingToPortrait.set(switchingToPortrait);
Apple2CrashHandler.getInstance().checkForCrashes(this);
boolean extperm = true;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// On Marshmallow+ specifically ask for permission to read/write storage
String readPermission = Manifest.permission.READ_EXTERNAL_STORAGE;
String writePermission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
int hasReadPermission = checkSelfPermission(readPermission);
int hasWritePermission = checkSelfPermission(writePermission);
int hasReadPermission = checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
int hasWritePermission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
ArrayList<String> permissions = new ArrayList<String>();
if (hasReadPermission != PackageManager.PERMISSION_GRANTED) {
permissions.add(readPermission);
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
if (hasWritePermission != PackageManager.PERMISSION_GRANTED) {
permissions.add(writePermission);
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!permissions.isEmpty()) {
extperm = false;
@@ -191,45 +179,49 @@ public class Apple2Activity extends Activity {
}
}
mGraphicsInitializedRunnable = new Runnable() {
@Override
public void run() {
if (firstTime) {
Apple2Preferences.KeypadPreset.IJKM_SPACE.apply(Apple2Activity.this);
}
Apple2Preferences.loadPreferences(Apple2Activity.this);
}
};
// first-time initializations
final boolean externalStoragePermission = extperm;
if (firstTime) {
new Thread(new Runnable() {
@Override
public void run() {
Apple2DisksMenu.exposeAPKAssets(Apple2Activity.this);
Apple2Utils.exposeAPKAssets(Apple2Activity.this);
if (externalStoragePermission) {
Apple2DisksMenu.exposeAPKAssetsToExternal(Apple2Activity.this);
Apple2Utils.exposeAPKAssetsToExternal(Apple2Activity.this);
logMessage(LogType.DEBUG, TAG, "Finished first time copying #1...");
if (!(boolean)Apple2Preferences.getJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_RELEASE_NOTES, false)) {
Runnable myRunnable = new Runnable() {
@Override
public void run() {
showReleaseNotes();
}
};
new Handler(Apple2Activity.this.getMainLooper()).post(myRunnable);
}
}
mSplashScreen.setDismissable(true);
Log.d(TAG, "Finished first time copying...");
}
}).start();
}
mSettingsMenu = new Apple2SettingsMenu(this);
mDisksMenu = new Apple2DisksMenu(this);
}
Intent intent = getIntent();
String path = null;
if (intent != null) {
Uri data = intent.getData();
if (data != null) {
path = data.getPath();
}
}
if (path != null && Apple2DisksMenu.hasDiskExtension(path)) {
handleInsertDiskIntent(path);
@Override
public void onEmailerFinished() {
Apple2CrashHandler.getInstance().cleanCrashData(this);
}
@Override
public void onDisksChosen(DiskArgs args) {
final String name = args.name;
if (Apple2DisksMenu.hasDiskExtension(name) || Apple2DisksMenu.hasStateExtension(name)) {
sDisksChosen = args;
} else if (!name.equals("")) {
Toast.makeText(this, R.string.disk_insert_toast_cannot, Toast.LENGTH_SHORT).show();
}
}
@@ -245,25 +237,77 @@ public class Apple2Activity extends Activity {
}
}
if (grantedPermissions) {
// this will force copying APK files (now that we have permission
Apple2DisksMenu.exposeAPKAssetsToExternal(Apple2Activity.this);
// perform migration(s) and assets exposure now
Apple2Utils.migrateToExternalStorage(Apple2Activity.this);
Apple2Utils.exposeAPKAssetsToExternal(Apple2Activity.this);
logMessage(LogType.DEBUG, TAG, "Finished first time copying #2...");
} // else ... we keep nagging on app startup ...
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
if (!(boolean)Apple2Preferences.getJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_RELEASE_NOTES, false)) {
showReleaseNotes();
}
}
@Override
protected void onResume() {
super.onResume();
if (sNativeBarfed) {
Apple2CrashHandler.getInstance().abandonAllHope(this, sNativeBarfedThrowable);
return;
}
Log.d(TAG, "onResume()");
showSplashScreen(/*dismissable:*/true);
Apple2CrashHandler.getInstance().checkForCrashes(this); // NOTE : needs to be called again to clean-up
do {
if (sNativeBarfed) {
Apple2CrashHandler.getInstance().abandonAllHope(this, sNativeBarfedThrowable);
break;
}
logMessage(LogType.DEBUG, TAG, "onResume()");
showSplashScreen(/*dismissable:*/true);
if (mDisksMenu == null) {
break;
}
if (sDisksChosen == null) {
break;
}
DiskArgs args = sDisksChosen;
sDisksChosen = null;
if (args.pfd == null) {
break;
}
String name = args.name;
if (Apple2DisksMenu.hasStateExtension(name)) {
boolean restored = Apple2MainMenu.restoreEmulatorState(this, args);
dismissAllMenus();
if (!restored) {
Toast.makeText(this, R.string.state_not_restored, Toast.LENGTH_SHORT).show();
}
break;
}
final String[] prefices = {"content://com.android.externalstorage.documents/document", "content://com.android.externalstorage.documents", "content://com.android.externalstorage.documents", "content://"};
for (String prefix : prefices) {
if (name.startsWith(prefix)) {
name = name.substring(prefix.length());
break;
}
}
// strip out URL-encoded '/' directory separators
String nameLower = name.toLowerCase();
int idx = nameLower.lastIndexOf("%2f", /*fromIndex:*/name.length() - 3);
if (idx >= 0) {
name = name.substring(idx + 3);
}
mDisksMenu.showDiskInsertionAlertDialog(name, args);
} while (false);
}
@Override
@@ -278,7 +322,13 @@ public class Apple2Activity extends Activity {
return;
}
Log.d(TAG, "onPause()");
if (isEmulationPaused()) {
Apple2Preferences.save(this);
} else {
logMessage(LogType.DEBUG, TAG, "Letting native save preferences...");
}
logMessage(LogType.INFO, TAG, "onPause() ...");
if (mView != null) {
mView.onPause();
}
@@ -287,7 +337,8 @@ public class Apple2Activity extends Activity {
// Dismiss these popups to avoid android.view.WindowLeaked issues
synchronized (this) {
dismissAllMenus();
nativeEmulationPause();
dismissAllMenus(); // 2nd time should full exit calibration mode (if present)
pauseEmulation();
}
mPausing.set(false);
@@ -295,21 +346,23 @@ public class Apple2Activity extends Activity {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (sNativeBarfed) {
return true;
if (Apple2Activity.isNativeBarfed()) {
return super.onKeyDown(keyCode, event);
}
if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) || (keyCode == KeyEvent.KEYCODE_VOLUME_UP)) {
return false;
return super.onKeyDown(keyCode, event);
}
nativeOnKeyDown(keyCode, event.getMetaState());
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (sNativeBarfed) {
return true;
if (Apple2Activity.isNativeBarfed()) {
return super.onKeyUp(keyCode, event);
}
if (keyCode == KeyEvent.KEYCODE_BACK) {
Apple2MenuView apple2MenuView = peekApple2View();
if (apple2MenuView == null) {
@@ -322,106 +375,11 @@ public class Apple2Activity extends Activity {
showMainMenu();
return true;
} else if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) || (keyCode == KeyEvent.KEYCODE_VOLUME_UP)) {
return false;
} else {
nativeOnKeyUp(keyCode, event.getMetaState());
return true;
return super.onKeyUp(keyCode, event);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
do {
if (sNativeBarfed) {
break;
}
if (mMainMenu == null) {
break;
}
Apple2MenuView apple2MenuView = peekApple2View();
if ((apple2MenuView != null) && (!apple2MenuView.isCalibrating())) {
break;
}
//printSamples(event);
int action = event.getActionMasked();
int pointerIndex = event.getActionIndex();
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount/* && i < MAX_FINGERS */; i++) {
mXCoords[i] = event.getX(i);
mYCoords[i] = event.getY(i);
}
long nativeFlags = nativeOnTouch(action, pointerCount, pointerIndex, mXCoords, mYCoords);
if ((nativeFlags & NATIVE_TOUCH_HANDLED) == 0) {
break;
}
if ((nativeFlags & NATIVE_TOUCH_REQUEST_SHOW_MENU) != 0) {
mMainMenu.show();
}
if ((nativeFlags & NATIVE_TOUCH_KEY_TAP) != 0) {
if (Apple2Preferences.KEYBOARD_CLICK_ENABLED.booleanValue(this)) {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (am != null) {
am.playSoundEffect(AudioManager.FX_KEY_CLICK);
}
}
if ((apple2MenuView != null) && apple2MenuView.isCalibrating()) {
long asciiScancodeLong = nativeFlags & (NATIVE_TOUCH_ASCII_SCANCODE_MASK << NATIVE_TOUCH_ASCII_SCANCODE_SHIFT);
int asciiInt = (int) (asciiScancodeLong >> (NATIVE_TOUCH_ASCII_SCANCODE_SHIFT + 8));
int scancode = (int) ((asciiScancodeLong >> NATIVE_TOUCH_ASCII_SCANCODE_SHIFT) & 0xFFL);
char ascii = (char) asciiInt;
apple2MenuView.onKeyTapCalibrationEvent(ascii, scancode);
}
}
if ((nativeFlags & NATIVE_TOUCH_MENU) == 0) {
break;
}
// handle menu-specific actions
if ((nativeFlags & NATIVE_TOUCH_INPUT_DEVICE_CHANGED) != 0) {
Apple2Preferences.TouchDeviceVariant nextVariant;
if ((nativeFlags & NATIVE_TOUCH_KBD) != 0) {
nextVariant = Apple2Preferences.TouchDeviceVariant.KEYBOARD;
} else if ((nativeFlags & NATIVE_TOUCH_JOY) != 0) {
nextVariant = Apple2Preferences.TouchDeviceVariant.JOYSTICK;
} else if ((nativeFlags & NATIVE_TOUCH_JOY_KPAD) != 0) {
nextVariant = Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD;
} else {
int touchDevice = Apple2Preferences.nativeGetCurrentTouchDevice();
nextVariant = Apple2Preferences.TouchDeviceVariant.next(touchDevice);
}
Apple2Preferences.CURRENT_TOUCH_DEVICE.saveTouchDevice(this, nextVariant);
} else if ((nativeFlags & NATIVE_TOUCH_CPU_SPEED_DEC) != 0) {
int percentSpeed = Apple2Preferences.nativeGetCPUSpeed();
if (percentSpeed > 400) { // HACK: max value from native side
percentSpeed = 375;
} else if (percentSpeed > 100) {
percentSpeed -= 25;
} else {
percentSpeed -= 5;
}
Apple2Preferences.CPU_SPEED_PERCENT.saveInt(this, percentSpeed);
} else if ((nativeFlags & NATIVE_TOUCH_CPU_SPEED_INC) != 0) {
int percentSpeed = Apple2Preferences.nativeGetCPUSpeed();
if (percentSpeed >= 100) {
percentSpeed += 25;
} else {
percentSpeed += 5;
}
Apple2Preferences.CPU_SPEED_PERCENT.saveInt(this, percentSpeed);
}
} while (false);
return super.onTouchEvent(event);
nativeOnKeyUp(keyCode, event.getMetaState());
return true;
}
public void showMainMenu() {
@@ -432,6 +390,59 @@ public class Apple2Activity extends Activity {
}
}
public void showReleaseNotes() {
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_RELEASE_NOTES, true);
Apple2Preferences.save(this);
String releaseNotes = "";
final int maxAttempts = 5;
int attempts = 0;
do {
InputStream is = null;
try {
AssetManager assetManager = getAssets();
is = assetManager.open("release_notes.txt");
final int bufferSize = 4096;
final char[] buffer = new char[bufferSize];
final StringBuilder out = new StringBuilder();
Reader in = new InputStreamReader(is, "UTF-8");
while (true) {
int siz = in.read(buffer, 0, buffer.length);
if (siz < 0) {
break;
}
out.append(buffer, 0, siz);
}
releaseNotes = out.toString();
break;
} catch (InterruptedIOException e) {
/* EINTR, EAGAIN */
} catch (IOException e) {
logMessage(LogType.ERROR, TAG, "OOPS could not load release_notes.txt : " + e.getMessage());
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
// NOOP
}
}
}
++attempts;
} while (attempts < maxAttempts);
AlertDialog.Builder builder = new AlertDialog.Builder(Apple2Activity.this).setIcon(R.drawable.ic_launcher).setCancelable(false).setTitle(R.string.release_notes).setMessage(releaseNotes).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
registerAndShowDialog(dialog);
}
public Apple2MainMenu getMainMenu() {
return mMainMenu;
}
@@ -444,59 +455,6 @@ public class Apple2Activity extends Activity {
return mSettingsMenu;
}
private void handleInsertDiskIntent(final String path) {
runOnUiThread(new Runnable() {
@Override
public void run() {
synchronized (Apple2Activity.this) {
if (mMainMenu == null) {
return;
}
String diskPath = path;
File diskFile = new File(diskPath);
if (!diskFile.canRead()) {
Toast.makeText(Apple2Activity.this, Apple2Activity.this.getString(R.string.disk_insert_could_not_read), Toast.LENGTH_SHORT).show();
return;
}
Apple2Preferences.CURRENT_DISK_A_RO.saveBoolean(Apple2Activity.this, true);
final int len = diskPath.length();
final String suffix = diskPath.substring(len - 3, len);
if (suffix.equalsIgnoreCase(".gz")) { // HACK FIXME TODO : small amount of code duplication of Apple2DisksMenu
diskPath = diskPath.substring(0, len - 3);
}
Apple2Preferences.CURRENT_DISK_A.saveString(Apple2Activity.this, diskPath);
while (mDisksMenu.popPathStack() != null) {
/* ... */
}
File storageDir = Apple2DisksMenu.getExternalStorageDirectory(Apple2Activity.this);
if (storageDir != null) {
String storagePath = storageDir.getAbsolutePath();
if (diskPath.contains(storagePath)) {
diskPath = diskPath.replace(storagePath + File.separator, "");
mDisksMenu.pushPathStack(storagePath);
}
}
StringTokenizer tokenizer = new StringTokenizer(diskPath, File.separator);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (token.equals("")) {
continue;
}
if (Apple2DisksMenu.hasDiskExtension(token)) {
continue;
}
mDisksMenu.pushPathStack(token);
}
Toast.makeText(Apple2Activity.this, Apple2Activity.this.getString(R.string.disk_insert_toast), Toast.LENGTH_SHORT).show();
}
}
});
}
public Apple2SplashScreen getSplashScreen() {
return mSplashScreen;
}
@@ -505,6 +463,7 @@ public class Apple2Activity extends Activity {
if (mSplashScreen != null) {
return;
}
mSplashScreen = new Apple2SplashScreen(this, dismissable);
mSplashScreen.show();
}
@@ -514,8 +473,7 @@ public class Apple2Activity extends Activity {
boolean glViewFirstTime = false;
if (mView == null) {
glViewFirstTime = true;
mView = new Apple2View(this, mGraphicsInitializedRunnable);
mGraphicsInitializedRunnable = null;
mView = new Apple2View(this);
mMainMenu = new Apple2MainMenu(this, mView);
}
@@ -533,23 +491,13 @@ public class Apple2Activity extends Activity {
}
public synchronized void pushApple2View(Apple2MenuView apple2MenuView) {
//
mMenuStack.add(apple2MenuView);
View menuView = apple2MenuView.getView();
nativeEmulationPause();
pauseEmulation();
addContentView(menuView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
}
public synchronized Apple2MenuView popApple2View() {
int lastIndex = mMenuStack.size() - 1;
if (lastIndex < 0) {
return null;
}
Apple2MenuView apple2MenuView = mMenuStack.remove(lastIndex);
_disposeApple2View(apple2MenuView);
return apple2MenuView;
}
public synchronized Apple2MenuView peekApple2View() {
int lastIndex = mMenuStack.size() - 1;
if (lastIndex < 0) {
@@ -620,38 +568,50 @@ public class Apple2Activity extends Activity {
if (dismissedSplashScreen) {
setupGLView();
} else {
nativeEmulationResume();
maybeResumeEmulation();
}
}
}
}
public void maybeResumeCPU() {
public boolean isEmulationPaused() {
boolean mainMenuShowing = (mMainMenu != null && mMainMenu.isShowing());
boolean menusShowing = (mMenuStack.size() > 0);
return mainMenuShowing || menusShowing;
}
public void maybeResumeEmulation() {
if (mMenuStack.size() == 0 && !mPausing.get()) {
Apple2Preferences.sync(this, null);
nativeEmulationResume();
}
}
public void maybeRebootQuit() {
nativeEmulationPause();
public void pauseEmulation() {
boolean previouslyRunning = nativeEmulationPause();
if (previouslyRunning) {
Apple2Preferences.load(this);
}
}
AlertDialog rebootQuitDialog = new AlertDialog.Builder(this).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.quit_reboot).setMessage(R.string.quit_reboot_choice).setPositiveButton(R.string.reboot, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
nativeReboot();
Apple2Activity.this.mMainMenu.dismiss();
}
}).setNeutralButton(R.string.quit, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
quitEmulator();
}
}).setNegativeButton(R.string.cancel, null).create();
public void rebootEmulation(int resetState) {
nativeReboot(resetState);
}
registerAndShowDialog(rebootQuitDialog);
public void saveState(String saveStateJson) {
nativeSaveState(saveStateJson);
}
public String stateExtractDiskPaths(String extractStateJson) {
return nativeStateExtractDiskPaths(extractStateJson);
}
public String loadState(String loadStateJson) {
return nativeLoadState(loadStateJson);
}
public void quitEmulator() {
logMessage(LogType.INFO, TAG, "Quitting...");
nativeOnQuit();
finish();
new Runnable() {
@@ -667,40 +627,29 @@ public class Apple2Activity extends Activity {
}.run();
}
public void maybeSaveRestore() {
nativeEmulationPause();
public enum LogType {
// Constants match
VERBOSE(2),
DEBUG(3),
INFO(4),
WARN(5),
ERROR(6);
private int type;
final String quickSavePath = Apple2DisksMenu.getDataDir(this) + File.separator + SAVE_FILE;
LogType(int type) {
this.type = type;
}
}
AlertDialog saveRestoreDialog = new AlertDialog.Builder(this).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.saverestore).setMessage(R.string.saverestore_choice).setPositiveButton(R.string.save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Apple2Activity.this.nativeSaveState(quickSavePath);
Apple2Activity.this.mMainMenu.dismiss();
}
}).setNeutralButton(R.string.restore, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// loading state can change the disk inserted ... reflect that in
String jsonData = Apple2Activity.this.nativeLoadState(quickSavePath);
try {
JSONObject map = new JSONObject(jsonData);
String diskPath1 = map.getString("disk1");
boolean readOnly1 = map.getBoolean("readOnly1");
Apple2Preferences.CURRENT_DISK_A.setPath(Apple2Activity.this, diskPath1);
Apple2Preferences.CURRENT_DISK_A_RO.saveBoolean(Apple2Activity.this, readOnly1);
String diskPath2 = map.getString("disk2");
boolean readOnly2 = map.getBoolean("readOnly2");
Apple2Preferences.CURRENT_DISK_B.setPath(Apple2Activity.this, diskPath2);
Apple2Preferences.CURRENT_DISK_B_RO.saveBoolean(Apple2Activity.this, readOnly2);
} catch (JSONException je) {
Log.v(TAG, "OOPS : "+je);
}
Apple2Activity.this.mMainMenu.dismiss();
}
}).setNegativeButton(R.string.cancel, null).create();
registerAndShowDialog(saveRestoreDialog);
public static void logMessage(LogType type, String tag, String mesg) {
JSONObject map = new JSONObject();
try {
map.put("type", type.type);
map.put("tag", tag);
map.put("mesg", mesg);
nativeLogMessage(map.toString());
} catch (Exception e) {
Log.e(TAG, "OOPS: " + e.getMessage());
}
}
}

View File

@@ -17,13 +17,19 @@ import android.widget.CompoundButton;
import android.widget.TextView;
import org.deadc0de.apple2ix.basic.R;
import org.json.JSONArray;
public class Apple2AudioSettingsMenu extends Apple2AbstractMenu {
private final static String TAG = "Apple2AudioSettingsMenu";
public final static int AUDIO_LATENCY_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES;
private static int sSampleRateCanary = 0;
public Apple2AudioSettingsMenu(Apple2Activity activity) {
super(activity);
sSampleRateCanary = DevicePropertyCalculator.getRecommendedSampleRate(activity);
}
@Override
@@ -49,6 +55,27 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu {
return position == SETTINGS.MOCKINGBOARD_ENABLED.ordinal();
}
public enum Volume {
OFF(0),
ONE(1),
TWO(2),
THREE(3),
FOUR(4),
MEDIUM(5),
FIVE(5),
SIX(6),
SEVEN(7),
EIGHT(8),
NINE(9),
MAX(10),
ELEVEN(11); // Ours goes to eleven...
private int vol;
Volume(int vol) {
this.vol = vol;
}
}
enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
SPEAKER_VOLUME {
@Override
@@ -61,17 +88,28 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.speaker_volume_summary);
}
@Override
public String getPrefKey() {
return "speakerVolume";
}
@Override
public Object getPrefDefault() {
return Volume.MEDIUM.ordinal();
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.Volume.MAX.ordinal() - 1, new IPreferenceSlider() {
final IMenuEnum self = this;
return _sliderView(activity, this, Volume.MAX.ordinal() - 1, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.SPEAKER_VOLUME.saveVolume(activity, Apple2Preferences.Volume.values()[progress]);
Apple2Preferences.setJSONPref(self, Volume.values()[progress].ordinal());
}
@Override
public int intValue() {
return Apple2Preferences.SPEAKER_VOLUME.intValue(activity);
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
@@ -92,14 +130,25 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.mockingboard_enable_summary);
}
@Override
public String getPrefKey() {
return "mbEnabled";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.MOCKINGBOARD_ENABLED.booleanValue(activity));
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.MOCKINGBOARD_ENABLED.saveBoolean(activity, isChecked);
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
@@ -116,17 +165,28 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.mockingboard_volume_summary);
}
@Override
public String getPrefKey() {
return "mbVolume";
}
@Override
public Object getPrefDefault() {
return Volume.MEDIUM.ordinal();
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.Volume.MAX.ordinal() - 1, new IPreferenceSlider() {
final IMenuEnum self = this;
return _sliderView(activity, this, Volume.MAX.ordinal() - 1, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.MOCKINGBOARD_VOLUME.saveVolume(activity, Apple2Preferences.Volume.values()[progress]);
Apple2Preferences.setJSONPref(self, Volume.values()[progress].ordinal());
}
@Override
public int intValue() {
return Apple2Preferences.MOCKINGBOARD_VOLUME.intValue(activity);
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
@@ -158,26 +218,46 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.audio_latency_summary);
}
@Override
public String getPrefKey() {
return "audioLatency";
}
@Override
public Object getPrefDefault() {
int defaultLatency;
if (Apple2AudioSettingsMenu.sSampleRateCanary == DevicePropertyCalculator.defaultSampleRate) {
// quite possibly an audio-challenged device
defaultLatency = 8; // /AUDIO_LATENCY_NUM_CHOICES -> 0.4f
} else {
// reasonable default for high-end devices
defaultLatency = 5; // /AUDIO_LATENCY_NUM_CHOICES -> 0.25f
}
return ((float) defaultLatency) / AUDIO_LATENCY_NUM_CHOICES;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.AUDIO_LATENCY_NUM_CHOICES, new IPreferenceSlider() {
final IMenuEnum self = this;
return _sliderView(activity, this, AUDIO_LATENCY_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
if (progress == 0) {
// disallow 0-length buffer ...
progress = 1;
}
Apple2Preferences.AUDIO_LATENCY.saveInt(activity, progress);
Apple2Preferences.setJSONPref(self, ((float) progress) / AUDIO_LATENCY_NUM_CHOICES);
}
@Override
public int intValue() {
return Apple2Preferences.AUDIO_LATENCY.intValue(activity);
float pref = Apple2Preferences.getFloatJSONPref(self);
return (int) (pref * AUDIO_LATENCY_NUM_CHOICES);
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
seekBarValue.setText("" + ((float) progress / Apple2Preferences.AUDIO_LATENCY_NUM_CHOICES));
seekBarValue.setText("" + ((float) progress / AUDIO_LATENCY_NUM_CHOICES));
}
});
}
@@ -185,9 +265,23 @@ public class Apple2AudioSettingsMenu extends Apple2AbstractMenu {
public static final int size = SETTINGS.values().length;
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_AUDIO;
}
@Override
public String getPrefKey() {
return null;
}
@Override
public Object getPrefDefault() {
return null;
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
/* ... */
}
@Override

View File

@@ -11,38 +11,99 @@
package org.deadc0de.apple2ix;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import org.deadc0de.apple2ix.basic.BuildConfig;
import org.deadc0de.apple2ix.basic.R;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
public class Apple2CrashHandler {
public final static String ALL_CRASH_FILE = "apple2ix_data.zip";
public final static String javaCrashFileName = "jcrash.txt";
public static Apple2CrashHandler getInstance() {
return sCrashHandler;
}
public enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
GL_VENDOR {
@Override
public String getPrefKey() {
return "glVendor";
}
@Override
public Object getPrefDefault() {
return "unknown";
}
},
GL_RENDERER {
@Override
public String getPrefKey() {
return "glRenderer";
}
@Override
public Object getPrefDefault() {
return "unknown";
}
},
GL_VERSION {
@Override
public String getPrefKey() {
return "glVersion";
}
@Override
public Object getPrefDefault() {
return "unknown";
}
};
@Override
public final String getTitle(Apple2Activity activity) {
throw new RuntimeException();
}
@Override
public final String getSummary(Apple2Activity activity) {
throw new RuntimeException();
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_INTERFACE;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
throw new RuntimeException();
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
throw new RuntimeException();
}
}
public enum CrashType {
JAVA_CRASH {
@Override
@@ -67,6 +128,18 @@ public class Apple2CrashHandler {
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.crash_stackbuf_overflow);
}
},
SIGABRT {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.crash_sigabrt);
}
},
SIGFPE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.crash_sigfpe);
}
};
@@ -87,7 +160,7 @@ public class Apple2CrashHandler {
public synchronized void initializeAndSetCustomExceptionHandler(Apple2Activity activity) {
synchronized (this) {
if (homeDir == null) {
homeDir = Apple2DisksMenu.getDataDir(activity);
homeDir = Apple2Utils.getDataDir(activity);
}
}
if (mDefaultExceptionHandler != null) {
@@ -131,212 +204,127 @@ public class Apple2CrashHandler {
}
public void checkForCrashes(final Apple2Activity activity) {
File oldCrashFile = _getCrashLogFile(activity);
if (oldCrashFile.exists()) {
oldCrashFile.delete();
}
if (!areCrashesPresent(activity)) {
return;
}
if (!Apple2Preferences.CRASH_CHECK.booleanValue(activity)) {
Apple2Preferences.load(activity);
if (!(boolean) Apple2Preferences.getJSONPref(Apple2SettingsMenu.SETTINGS.CRASH)) {
cleanCrashData(activity);
return;
}
boolean previouslyRanCrashCheck = mAlreadyRanCrashCheck.getAndSet(true);
boolean previouslySentReport = mAlreadySentReport.get();
if (previouslySentReport) {
// here we assume that the crash data was previously sent via email ... if not then we lost it =P
Log.d(TAG, "Cleaning up crash data ...");
int idx = 0;
File[] nativeCrashes = _nativeCrashFiles(activity);
for (File crash : nativeCrashes) {
if (!crash.delete()) {
Log.d(TAG, "Could not unlink crash : " + crash);
}
File processed = new File(_dumpPath2ProcessedPath(crash.getAbsolutePath()));
if (!processed.delete()) {
Log.d(TAG, "Could not unlink processed : " + processed);
}
}
File javaCrashFile = _javaCrashFile(activity);
if (!javaCrashFile.delete()) {
Log.d(TAG, "Could not unlink java crash : " + javaCrashFile);
}
// remove previous log file
_writeTempLogFile(activity, new StringBuilder());
return;
}
if (previouslyRanCrashCheck) {
// don't keep asking on return from backgrounding
return;
}
final AlertDialog crashDialog = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.crasher_send).setMessage(R.string.crasher_send_message).setNegativeButton(R.string.no, null).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
final AlertDialog crashDialog = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.crasher_send).setMessage(R.string.crasher_send_message).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
emailCrashesAndLogs(activity);
}
}).create();
activity.registerAndShowDialog(crashDialog);
}
public void emailCrashesAndLogs(final Apple2Activity activity) {
final Apple2SplashScreen splashScreen = activity.getSplashScreen();
if (splashScreen != null) {
splashScreen.setDismissable(false);
}
final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar);
try {
bar.setVisibility(View.VISIBLE);
} catch (NullPointerException npe) {
/* email logs doesn't show the splash screen */
}
new Thread(new Runnable() {
@Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
final int sampleRate = DevicePropertyCalculator.getRecommendedSampleRate(activity);
final int monoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(activity, /*isStereo:*/false);
final int stereoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(activity, /*isStereo:*/true);
StringBuilder summary = new StringBuilder();
// prepend information about this device
summary.append("BRAND: ").append(Build.BRAND).append("\n");
summary.append("MODEL: ").append(Build.MODEL).append("\n");
summary.append("MANUFACTURER: ").append(Build.MANUFACTURER).append("\n");
summary.append("DEVICE: ").append(Build.DEVICE).append("\n");
summary.append("SDK: ").append(Build.VERSION.SDK_INT).append("\n");
summary.append("SAMPLE RATE: ").append(sampleRate).append("\n");
summary.append("MONO BUFSIZE: ").append(monoBufferSize).append("\n");
summary.append("STEREO BUFSIZE: ").append(stereoBufferSize).append("\n");
summary.append("GPU VENDOR: ").append(Apple2Preferences.getJSONPref(SETTINGS.GL_VENDOR)).append("\n");
summary.append("GPU RENDERER: ").append(Apple2Preferences.getJSONPref(SETTINGS.GL_RENDERER)).append("\n");
summary.append("GPU VERSION: ").append(Apple2Preferences.getJSONPref(SETTINGS.GL_VERSION)).append("\n");
final Apple2SplashScreen splashScreen = activity.getSplashScreen();
if (splashScreen != null) {
splashScreen.setDismissable(false);
}
final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar);
try {
bar.setVisibility(View.VISIBLE);
} catch (NullPointerException npe) {
/* could happen on early lifecycle crashes */
PackageInfo pInfo = activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0);
summary.append("APP VERSION: ").append(pInfo.versionName).append("\n");
} catch (PackageManager.NameNotFoundException e) {
// ...
}
new Thread(new Runnable() {
File[] nativeCrashes = _nativeCrashFiles(activity);
if (nativeCrashes == null) {
nativeCrashes = new File[0];
}
final int len = nativeCrashes.length + 1/* maybe Java crash */ + 1/* exposeSymbols */;
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (bar != null) {
bar.setMax(len);
}
}
});
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
if (nativeCrashes.length > 0) {
Apple2Utils.exposeSymbols(activity);
}
final int sampleRate = DevicePropertyCalculator.getRecommendedSampleRate(activity);
final int monoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(activity, /*isStereo:*/false);
final int stereoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(activity, /*isStereo:*/true);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (bar != null) {
bar.setProgress(1);
}
}
});
StringBuilder summary = new StringBuilder();
StringBuilder allCrashData = new StringBuilder();
// iteratively process native crashes
ArrayList<File> allCrashFiles = new ArrayList<File>();
if (nativeCrashes.length > 0) {
for (File crash : nativeCrashes) {
// prepend information about this device
summary.append("BRAND: ").append(Build.BRAND).append("\n");
summary.append("MODEL: ").append(Build.MODEL).append("\n");
summary.append("MANUFACTURER: ").append(Build.MANUFACTURER).append("\n");
summary.append("DEVICE: ").append(Build.DEVICE).append("\n");
summary.append("SDK: ").append(Build.VERSION.SDK_INT).append("\n");
summary.append("SAMPLE RATE: ").append(sampleRate).append("\n");
summary.append("MONO BUFSIZE: ").append(monoBufferSize).append("\n");
summary.append("STEREO BUFSIZE: ").append(stereoBufferSize).append("\n");
summary.append("GPU VENDOR: ").append(Apple2Preferences.GL_VENDOR.stringValue(activity)).append("\n");
summary.append("GPU RENDERER: ").append(Apple2Preferences.GL_RENDERER.stringValue(activity)).append("\n");
summary.append("GPU VERSION: ").append(Apple2Preferences.GL_VERSION.stringValue(activity)).append("\n");
String crashPath = crash.getAbsolutePath();
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Processing crash : " + crashPath);
allCrashData.append(summary);
File[] nativeCrashes = _nativeCrashFiles(activity);
if (nativeCrashes == null) {
nativeCrashes = new File[0];
String processedPath = _dumpPath2ProcessedPath(crashPath);
try {
nativeProcessCrash(crashPath, processedPath); // Run Breakpad minidump_stackwalk
} catch (UnsatisfiedLinkError ule) {
/* could happen on early lifecycle crashes */
}
final int len = nativeCrashes.length + 1/* maybe Java crash */ + 1/* exposeSymbols */;
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (bar != null) {
bar.setMax(len);
}
}
});
if (len > 0) {
Apple2DisksMenu.exposeSymbols(activity);
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (bar != null) {
bar.setProgress(1);
}
}
});
boolean summarizedHeader = false;
// iteratively process native crashes
for (File crash : nativeCrashes) {
String crashPath = crash.getAbsolutePath();
Log.d(TAG, "Processing crash : " + crashPath);
String processedPath = _dumpPath2ProcessedPath(crashPath);
try {
nativeProcessCrash(crashPath, processedPath); // Run Breakpad minidump_stackwalk
} catch (UnsatisfiedLinkError ule) {
/* could happen on early lifecycle crashes */
}
StringBuilder crashData = new StringBuilder();
if (!_readFile(new File(processedPath), crashData)) {
Log.e(TAG, "Error processing crash : " + crashPath);
}
allCrashData.append(">>>>>>> NATIVE CRASH [").append(crashPath).append("]\n");
allCrashData.append(crashData);
summary.append("NATIVE CRASH:\n");
// append succinct information about crashing thread
String[] lines = crashData.toString().split("[\\n\\r][\\n\\r]*");
for (int i = 0, j = 0; i < lines.length; i++) {
// 2 lines of minidump summary
if (i < 2) {
if (!summarizedHeader) {
summary.append(lines[i]);
summary.append("\n");
}
continue;
}
// 1 line of crashing thread and reason
if (i == 2) {
summarizedHeader = true;
summary.append(lines[i]);
summary.append("\n");
continue;
}
// whole lotta modules
if (lines[i].startsWith("Module")) {
continue;
}
// one apparently empty line
if (lines[i].matches("^[ \\t]*$")) {
continue;
}
// append crashing thread backtrace
summary.append(lines[i]);
summary.append("\n");
final int maxSummaryBacktrace = 8;
if (j++ >= maxSummaryBacktrace) {
break;
}
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (bar != null) {
bar.setProgress(bar.getProgress() + 1);
}
}
});
}
StringBuilder javaCrashData = new StringBuilder();
File javaCrashFile = _javaCrashFile(activity);
if (javaCrashFile.exists()) {
Log.d(TAG, "Reading java crashes file");
if (!_readFile(javaCrashFile, javaCrashData)) {
Log.e(TAG, "Error processing java crash : " + javaCrashFileName);
}
}
allCrashData.append(">>>>>>> JAVA CRASH DATA\n");
allCrashData.append(javaCrashData);
summary.append("JAVA CRASH:\n");
summary.append(javaCrashData);
allCrashFiles.add(new File(processedPath));
activity.runOnUiThread(new Runnable() {
@Override
@@ -346,27 +334,85 @@ public class Apple2CrashHandler {
}
}
});
Apple2DisksMenu.unexposeSymbols(activity);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
bar.setVisibility(View.INVISIBLE);
splashScreen.setDismissable(true);
} catch (NullPointerException npe) {
/* could happen on early lifecycle crashes */
}
}
});
// send report with all the data
_sendEmailToDeveloperWithCrashData(activity, summary, allCrashData);
}
}).start();
summary.append("" + nativeCrashes.length + " Native dumps\n");
}
File javaCrashFile = _javaCrashFile(activity);
if (javaCrashFile.exists()) {
summary.append("Java crash log\n");
allCrashFiles.add(javaCrashFile);
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (bar != null) {
bar.setProgress(bar.getProgress() + 1);
}
}
});
allCrashFiles.add(new File(homeDir, Apple2Preferences.PREFS_FILE));
if (nativeCrashes.length > 0) {
Apple2Utils.unexposeSymbols(activity);
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
bar.setVisibility(View.INVISIBLE);
splashScreen.setDismissable(true);
} catch (NullPointerException npe) {
/* email logs doesn't show the splash screen */
}
}
});
// Add all the log files ...
{
File[] nativeLogs = _nativeLogs(activity);
for (File logFile : nativeLogs) {
allCrashFiles.add(logFile);
}
}
File[] allCrashesAry = new File[allCrashFiles.size()];
File nativeCrashesZip = Apple2Utils.zipFiles(allCrashFiles.toArray(allCrashesAry), _getCrashLogFile(activity));
// send report with all the data
_sendEmailToDeveloperWithCrashData(activity, summary, nativeCrashesZip);
}
}).create();
activity.registerAndShowDialog(crashDialog);
}).start();
}
public void cleanCrashData(Apple2Activity activity) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Cleaning up crash data ...");
File[] nativeCrashes = _nativeCrashFiles(activity);
for (File crash : nativeCrashes) {
if (!crash.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink crash : " + crash);
}
File processed = new File(_dumpPath2ProcessedPath(crash.getAbsolutePath()));
if (!processed.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink processed : " + processed);
}
}
File javaCrashFile = _javaCrashFile(activity);
if (!javaCrashFile.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink java crash : " + javaCrashFile);
}
/* HACK NOTE -- don't delete the crash log file here ... possibly the email sender program still needs the link!
File allCrashFile = _getCrashLogFile(activity);
Apple2Utils.writeFile(new StringBuilder(), allCrashFile);
allCrashFile.delete();
*/
}
public void performCrash(int crashType) {
@@ -411,7 +457,7 @@ public class Apple2CrashHandler {
} catch (InterruptedIOException ie) {
/* EINTR, EAGAIN ... */
} catch (IOException e) {
Log.e(TAG, "Exception attempting to write data : " + e);
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Exception attempting to write data : " + e);
}
try {
@@ -427,6 +473,21 @@ public class Apple2CrashHandler {
return new File(homeDir, javaCrashFileName);
}
private File[] _nativeLogs(Apple2Activity activity) {
FilenameFilter logFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
File file = new File(dir, name);
if (file.isDirectory()) {
return false;
}
return name.startsWith("apple2ix_log");
}
};
return new File(homeDir).listFiles(logFilter);
}
private File[] _nativeCrashFiles(Apple2Activity activity) {
FilenameFilter dmpFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
@@ -456,115 +517,57 @@ public class Apple2CrashHandler {
return crashPath.substring(0, crashPath.length() - 4) + ".txt";
}
private boolean _readFile(File file, StringBuilder fileData) {
final int maxAttempts = 5;
int attempts = 0;
do {
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
char[] buf = new char[1024];
int numRead = 0;
while ((numRead = reader.read(buf)) != -1) {
String readData = String.valueOf(buf, 0, numRead);
fileData.append(readData);
}
reader.close();
break;
} catch (InterruptedIOException ie) {
/* EINTR, EAGAIN ... */
} catch (IOException e) {
Log.d(TAG, "Error reading file at path : " + file.toString());
}
try {
Thread.sleep(100, 0);
} catch (InterruptedException e) {
/* ... */
}
++attempts;
} while (attempts < maxAttempts);
return attempts < maxAttempts;
}
private File _writeTempLogFile(Apple2Activity activity, StringBuilder allCrashData) {
File allCrashFile = null;
private File _getCrashLogFile(Apple2Activity activity) {
File file;
String storageState = Environment.getExternalStorageState();
if (storageState.equals(Environment.MEDIA_MOUNTED)) {
allCrashFile = new File(Environment.getExternalStorageDirectory(), "apple2ix_crash.txt");
file = new File(Environment.getExternalStorageDirectory(), ALL_CRASH_FILE);
} else {
allCrashFile = new File(Apple2DisksMenu.getDataDir(activity), "apple2ix_crash.txt");
file = new File(Apple2Utils.getDataDir(activity), ALL_CRASH_FILE);
}
Log.d(TAG, "Writing all crashes to temp file : " + allCrashFile.getAbsolutePath());
final int maxAttempts = 5;
int attempts = 0;
do {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(allCrashFile));
writer.append(allCrashData);
writer.flush();
writer.close();
break;
} catch (InterruptedIOException ie) {
/* EINTR, EAGAIN ... */
} catch (IOException e) {
Log.e(TAG, "Exception attempting to write data : " + e);
}
try {
Thread.sleep(100, 0);
} catch (InterruptedException e) {
/* ... */
}
++attempts;
} while (attempts < maxAttempts);
if (!allCrashFile.setReadable(true, /*ownerOnly:*/false)) {
Log.d(TAG, "Oops, could not set all crash data readable!");
}
return allCrashFile;
return file;
}
private void _sendEmailToDeveloperWithCrashData(Apple2Activity activity, StringBuilder summary, StringBuilder allCrashData) {
mAlreadySentReport.set(true);
private void _sendEmailToDeveloperWithCrashData(Apple2Activity activity, StringBuilder summary, File nativeCrashesZip) {
final boolean alreadyChoosing = Apple2EmailerActivity.sEmailerIsEmailing.getAndSet(true);
if (alreadyChoosing) {
return;
}
// <sigh> ... the disaster that is early Android ... there does not appear to be a reliable way to start an
// email Intent to send both text and an attachment, but we make a valiant (if futile) effort to do so here.
// And the reason to send an attachment is that you trigger an android.os.TransactionTooLargeException with too
// much text data in the EXTRA_TEXT ... </sigh>
Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "apple2ix_crash@deadcode.org"/*non-zero variant is correct endpoint at the moment*/, null));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Crasher");
Intent emailIntent = new Intent(activity, Apple2EmailerActivity.class);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK/* | Intent.FLAG_ACTIVITY_CLEAR_TOP */);
final int maxCharsEmail = 4096;
int len = summary.length();
len = len < maxCharsEmail ? len : maxCharsEmail;
String summaryData = summary.substring(0, len);
emailIntent.putExtra(Intent.EXTRA_TEXT, "The app crashed, please help!\n\n"+summaryData);
emailIntent.putExtra(Intent.EXTRA_TEXT, "A2IX app logs and crash data\n\n" + summaryData);
File allCrashFile = _writeTempLogFile(activity, allCrashData);
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(allCrashFile));
if (!nativeCrashesZip.setReadable(true, /*ownerOnly:*/false)) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Oops, could not set crash file data readable!");
}
Log.d(TAG, "STARTING CHOOSER FOR EMAIL ...");
activity.startActivity(Intent.createChooser(emailIntent, "Send email"));
Log.d(TAG, "AFTER START ACTIVITY ...");
emailIntent.putExtra(Apple2EmailerActivity.EXTRA_STREAM_PATH, nativeCrashesZip.getAbsolutePath());
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "STARTING EMAILER ACTIVITY ...");
Apple2EmailerActivity.sEmailerCallback = activity;
activity.startActivityForResult(emailIntent, Apple2EmailerActivity.SEND_REQUEST_CODE);
}
private final static String TAG = "Apple2CrashHandler";
private final static Apple2CrashHandler sCrashHandler = new Apple2CrashHandler();
private String homeDir;
private Thread.UncaughtExceptionHandler mDefaultExceptionHandler;
private AtomicBoolean mAlreadyRanCrashCheck = new AtomicBoolean(false);
private AtomicBoolean mAlreadySentReport = new AtomicBoolean(false);
private static native void nativePerformCrash(int crashType); // testing
private static native void nativeProcessCrash(String crashFilePath, String crashProcessedPath);
}

View File

@@ -0,0 +1,226 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2017 Aaron Culliney
*
*/
package org.deadc0de.apple2ix;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.provider.OpenableColumns;
import java.util.concurrent.atomic.AtomicBoolean;
public class Apple2DiskChooserActivity extends AppCompatActivity {
public static final AtomicBoolean sDiskChooserIsChoosing = new AtomicBoolean(false);
public static Callback sDisksCallback;
public interface Callback {
void onDisksChosen(DiskArgs args);
}
@Nullable
public static ParcelFileDescriptor openFileDescriptorFromUri(Context ctx, Uri uri) {
ParcelFileDescriptor pfd = null;
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
throw new RuntimeException("SDK Version not allowed access");
}
if (!DocumentsContract.isDocumentUri(ctx, uri)) {
throw new RuntimeException("Not a Document URI for " + uri);
}
ContentResolver resolver = ctx.getContentResolver();
resolver.takePersistableUriPermission(uri, (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
pfd = resolver.openFileDescriptor(uri, "rw");
} catch (Throwable t) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS, could not get appropriate access to URI ( " + uri + " ) : " + t);
}
return pfd;
}
@Nullable
public static String getFileNameFromUri(Context ctx, Uri uri) {
String fileName = null;
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
throw new RuntimeException("SDK Version not allowed access");
}
if (!DocumentsContract.isDocumentUri(ctx, uri)) {
throw new RuntimeException("Not a Document URI for " + uri);
}
ContentResolver resolver = ctx.getContentResolver();
Cursor returnCursor = resolver.query(uri, null, null, null, null);
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
fileName = returnCursor.getString(nameIndex);
} catch (Throwable t) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS, could not get filename from URI ( " + uri + " ) : " + t);
}
return fileName;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putBoolean("ran", true);
super.onSaveInstanceState(outState);
}
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
Bundle b;
{
Intent intent = getIntent();
Bundle extras = null;
if (intent != null) {
extras = intent.getExtras();
}
if (savedState != null) {
b = savedState;
} else if (extras != null) {
b = extras;
} else {
b = new Bundle();
}
}
boolean ran = b.getBoolean("ran");
/* -- Android onCreate() can be called multiple times, for example, on an orientation change ... this codepath was aborting the disk selection process when an orientation event occurred...
if (ran) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS, already ran...");
finish();
return;
}
*/
Intent pickIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
try {
pickIntent.setType("*/*");
pickIntent.addCategory(Intent.CATEGORY_OPENABLE);
/* FIXME TODO : currently we don't have decent UI/UX for multi-select ...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
pickIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}*/
if (!ran) {
startActivityForResult(pickIntent, EDIT_REQUEST_CODE);
}
} catch (Throwable t) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS : " + t);
setResult(RESULT_CANCELED);
finish();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_CANCELED) {
/* FIXME TODO : currently we don't have decent UI/UX for multi-select ...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ClipData clipData = null;
if (data != null) {
clipData = data.getClipData();
}
if (clipData != null && clipData.getItemCount() > 1) {
uris = new Uri[2];
uris[0] = clipData.getItemAt(0).getUri();
uris[1] = clipData.getItemAt(1).getUri();
}
}*/
if (chosenUri == null) {
if (data != null) {
chosenUri = data.getData();
}
}
if (chosenUri != null) {
chosenPfd = openFileDescriptorFromUri(this, chosenUri);
chosenFileName = getFileNameFromUri(this, chosenUri);
}
}
setResult(RESULT_OK);
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void finish() {
sDiskChooserIsChoosing.set(false);
String name = chosenFileName == null ? (chosenUri == null ? "" : chosenUri.toString()) : chosenFileName;
if (sDisksCallback != null) {
sDisksCallback.onDisksChosen(new DiskArgs(name, chosenUri, chosenPfd));
}
super.finish();
}
private Uri chosenUri;
private ParcelFileDescriptor chosenPfd;
private String chosenFileName;
private static final String TAG = "A2DiskChooserActivity";
private static final int EDIT_REQUEST_CODE = 44;
}
class DiskArgs {
public String name;
public String path;
public Uri uri;
public ParcelFileDescriptor pfd;
public DiskArgs(String name, Uri uri, ParcelFileDescriptor pfd) {
this.name = name;
this.uri = uri;
this.pfd = pfd;
}
public DiskArgs(String path) {
this.path = path;
}
}

View File

@@ -11,14 +11,13 @@
package org.deadc0de.apple2ix;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.net.Uri;
import android.os.Build;
import androidx.appcompat.app.AlertDialog;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -26,41 +25,160 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RadioButton;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.Arrays;
import android.widget.TextView;
import android.widget.Toast;
import org.deadc0de.apple2ix.basic.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
public class Apple2DisksMenu implements Apple2MenuView {
private final static String TAG = "Apple2DisksMenu";
private static String sDataDir = null;
public final static String EXTERNAL_CHOOSER_SENTINEL = "apple2ix://";
private Apple2Activity mActivity = null;
private View mDisksView = null;
private final ArrayList<String> mPathStack = new ArrayList<String>();
private static File sExternalFilesDir = null;
private static File sDownloadFilesDir = null;
private static boolean sInitializedPath = false;
private static native String nativeChooseDisk(String jsonData);
private static native void nativeEjectDisk(boolean isDriveA);
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
CURRENT_DISK_SEARCH_PATH {
@Override
public String getPrefKey() {
return "diskPathStack";
}
@Override
public Object getPrefDefault() {
return new JSONArray();
}
},
CURRENT_DRIVE_A {
@Override
public String getPrefKey() {
return "driveACurrent";
}
@Override
public Object getPrefDefault() {
return true;
}
},
CURRENT_DISK_RO_BUTTON {
@Override
public String getPrefKey() {
return "driveRO";
}
@Override
public Object getPrefDefault() {
return false;
}
},
CURRENT_DISK_PATH_A {
@Override
public String getPrefKey() {
return "driveAInsertedDisk";
}
@Override
public Object getPrefDefault() {
return "";
}
},
CURRENT_DISK_PATH_A_RO {
@Override
public String getPrefKey() {
return "driveAInsertedDiskRO";
}
@Override
public Object getPrefDefault() {
return false;
}
},
CURRENT_DISK_PATH_B {
@Override
public String getPrefKey() {
return "driveBInsertedDisk";
}
@Override
public Object getPrefDefault() {
return "";
}
},
CURRENT_DISK_PATH_B_RO {
@Override
public String getPrefKey() {
return "driveBInsertedDiskRO";
}
@Override
public Object getPrefDefault() {
return false;
}
},
USE_NEWSCHOOL_DISK_SELECTION {
@Override
public String getPrefKey() {
return "useNewSchoolDiskSelection";
}
@Override
public Object getPrefDefault() {
// 2017/06/30 NOTE : keep this default false to accommodate initial installs that only have access to shipped public domain images
return false;
}
};
@Override
public final String getTitle(Apple2Activity activity) {
throw new RuntimeException();
}
@Override
public final String getSummary(Apple2Activity activity) {
throw new RuntimeException();
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_VM;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
throw new RuntimeException();
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
throw new RuntimeException();
}
}
public Apple2DisksMenu(Apple2Activity activity) {
mActivity = activity;
@@ -75,143 +193,45 @@ public class Apple2DisksMenu implements Apple2MenuView {
}
});
getExternalStorageDirectory(activity);
}
public static File getExternalStorageDirectory(Apple2Activity activity) {
do {
if (sExternalFilesDir != null) {
break;
}
String storageState = Environment.getExternalStorageState();
if (!storageState.equals(Environment.MEDIA_MOUNTED)) {
// 2015/10/28 : do not expose sExternalFilesDir/sDownloadFilesDir unless they are writable
break;
}
File externalStorageDir = Environment.getExternalStorageDirectory();
if (externalStorageDir == null) {
break;
}
File externalDir = new File(externalStorageDir, "apple2ix"); // /sdcard/apple2ix
if (!externalDir.exists()) {
boolean made = externalDir.mkdirs();
if (!made) {
Log.d(TAG, "WARNING: could not make directory : " + sExternalFilesDir);
break;
for (int i = 0; i < 2; i++) {
final Button ejectButton = (Button) mDisksView.findViewById(i == 0 ? R.id.ejectButton1 : R.id.ejectButton2);
final int idx = i;
ejectButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ejectDisk(/*isDriveA:*/idx == 0);
dynamicSetup();
}
}
sExternalFilesDir = externalDir;
sDownloadFilesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
} while (false);
return sExternalFilesDir;
}
// HACK NOTE 2015/02/22 : Apparently native code cannot easily access stuff in the APK ... so copy various resources
// out of the APK and into the /data/data/... for ease of access. Because this is FOSS software we don't care about
// security or DRM for these assets =)
public static String getDataDir(Apple2Activity activity) {
if (sDataDir != null) {
return sDataDir;
});
}
try {
PackageManager pm = activity.getPackageManager();
PackageInfo pi = pm.getPackageInfo(activity.getPackageName(), 0);
sDataDir = pi.applicationInfo.dataDir;
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "" + e);
if (sDataDir == null) {
sDataDir = "/data/local/tmp";
}
}
return sDataDir;
}
public static void exposeAPKAssetsToExternal(Apple2Activity activity) {
getExternalStorageDirectory(activity);
if (sExternalFilesDir == null) {
return;
}
final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar);
activity.runOnUiThread(new Runnable() {
final CheckBox newschoolSelection = (CheckBox) mDisksView.findViewById(R.id.newschoolDiskSelectionButton);
newschoolSelection.setOnClickListener(new View.OnClickListener() {
@Override
public void run() {
try {
bar.setVisibility(View.VISIBLE);
bar.setIndeterminate(true);
} catch (NullPointerException npe) {
Log.v(TAG, "Avoid NPE in exposeAPKAssetsToExternal #1");
}
public void onClick(View v) {
Apple2Preferences.setJSONPref(SETTINGS.USE_NEWSCHOOL_DISK_SELECTION, newschoolSelection.isChecked());
dynamicSetup();
}
});
Log.v(TAG, "Overwriting system files in /sdcard/apple2ix/ (external storage) ...");
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/sExternalFilesDir.getAbsolutePath());
activity.runOnUiThread(new Runnable() {
final View newschoolChooser = mDisksView.findViewById(R.id.disk_selection_newschool_chooser);
newschoolChooser.setOnClickListener(new View.OnClickListener() {
@Override
public void run() {
try {
bar.setVisibility(View.INVISIBLE);
bar.setIndeterminate(false);
} catch (NullPointerException npe) {
Log.v(TAG, "Avoid NPE in exposeAPKAssetsToExternal #2");
public void onClick(View v) {
final boolean alreadyChoosing = Apple2DiskChooserActivity.sDiskChooserIsChoosing.getAndSet(true);
if (alreadyChoosing) {
return;
}
}
});
}
public static void exposeAPKAssets(Apple2Activity activity) {
final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
bar.setVisibility(View.VISIBLE);
bar.setIndeterminate(true);
} catch (NullPointerException npe) {
Log.v(TAG, "Avoid NPE in exposeAPKAssets #1");
}
Intent chooserIntent = new Intent(mActivity, Apple2DiskChooserActivity.class);
chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION/* | Intent.FLAG_ACTIVITY_CLEAR_TOP */);
Apple2DiskChooserActivity.sDisksCallback = mActivity;
mActivity.startActivity(chooserIntent);
}
});
getDataDir(activity);
Log.d(TAG, "First time copying stuff-n-things out of APK for ease-of-NDK access...");
getExternalStorageDirectory(activity);
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"disks", /*to location:*/new File(sDataDir, "disks").getAbsolutePath());
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/new File(sDataDir, "keyboards").getAbsolutePath());
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"shaders", /*to location:*/new File(sDataDir, "shaders").getAbsolutePath());
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
bar.setVisibility(View.INVISIBLE);
bar.setIndeterminate(false);
} catch (NullPointerException npe) {
Log.v(TAG, "Avoid NPE in exposeAPKAssets #1");
}
}
});
}
public static void exposeSymbols(Apple2Activity activity) {
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"symbols", /*to location:*/new File(sDataDir, "symbols").getAbsolutePath());
}
public static void unexposeSymbols(Apple2Activity activity) {
recursivelyDelete(new File(sDataDir, "symbols"));
Apple2Utils.getExternalStorageDirectory(activity);
}
// ------------------------------------------------------------------------
@@ -231,14 +251,18 @@ public class Apple2DisksMenu implements Apple2MenuView {
}
if (!sInitializedPath) {
sInitializedPath = true;
Apple2Preferences.CURRENT_DISK_PATH.load(mActivity);
setPathStackJSON((JSONArray) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_SEARCH_PATH));
}
dynamicSetup();
mActivity.pushApple2View(this);
}
public void dismiss() {
String path = popPathStack();
String path = null;
if (!(boolean) Apple2Preferences.getJSONPref(SETTINGS.USE_NEWSCHOOL_DISK_SELECTION)) {
path = popPathStack();
}
if (path == null) {
mActivity.popApple2View(this);
} else {
@@ -263,15 +287,13 @@ public class Apple2DisksMenu implements Apple2MenuView {
// ------------------------------------------------------------------------
// path stack methods
public String getPathStackJSON() {
JSONArray jsonArray = new JSONArray(Arrays.asList(mPathStack.toArray()));
return jsonArray.toString();
public JSONArray getPathStackJSON() {
return new JSONArray(Arrays.asList(mPathStack.toArray()));
}
public void setPathStackJSON(String pathStackJSON) {
public void setPathStackJSON(JSONArray jsonArray) {
mPathStack.clear();
try {
JSONArray jsonArray = new JSONArray(pathStackJSON);
for (int i = 0, count = jsonArray.length(); i < count; i++) {
String pathComponent = jsonArray.getString(i);
mPathStack.add(pathComponent);
@@ -283,7 +305,7 @@ public class Apple2DisksMenu implements Apple2MenuView {
public void pushPathStack(String path) {
mPathStack.add(path);
Apple2Preferences.CURRENT_DISK_PATH.saveString(mActivity, getPathStackJSON());
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_SEARCH_PATH, getPathStackJSON());
}
public String popPathStack() {
@@ -291,10 +313,138 @@ public class Apple2DisksMenu implements Apple2MenuView {
return null;
}
String path = mPathStack.remove(mPathStack.size() - 1);
Apple2Preferences.CURRENT_DISK_PATH.saveString(mActivity, getPathStackJSON());
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_SEARCH_PATH, getPathStackJSON());
return path;
}
public static void insertDisk(Apple2Activity activity, DiskArgs diskArgs, boolean isDriveA, boolean isReadOnly, boolean onLaunch) {
try {
JSONObject map = new JSONObject();
ejectDisk(isDriveA);
String imageName = diskArgs.path;
if (imageName == null) {
imageName = EXTERNAL_CHOOSER_SENTINEL + diskArgs.uri.toString();
}
if (imageName.startsWith(EXTERNAL_CHOOSER_SENTINEL)) {
if (!Apple2Utils.isExternalStorageAccessible(activity)) {
// disallow access if we cannot access external storage
throw new Exception("External storage not accessible");
}
if (diskArgs.pfd == null) {
if (diskArgs.uri == null) {
String uriString = imageName.substring(EXTERNAL_CHOOSER_SENTINEL.length());
diskArgs.uri = Uri.parse(uriString);
}
diskArgs.pfd = Apple2DiskChooserActivity.openFileDescriptorFromUri(activity, diskArgs.uri);
}
int fd = diskArgs.pfd.getFd(); // NPE thrown if diskArgs.pfd is null
map.put("fd", fd);
} else {
File file = new File(imageName);
if (!file.exists()) {
throw new RuntimeException("cannot insert : " + imageName);
}
}
if (isDriveA) {
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_A_RO, isReadOnly);
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_A, imageName);
} else {
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_B_RO, isReadOnly);
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_B, imageName);
}
map.put("disk", imageName);
map.put("drive", isDriveA ? "0" : "1");
map.put("readOnly", isReadOnly ? "true" : "false");
String jsonString = nativeChooseDisk(map.toString());
if (diskArgs.pfd != null) {
try {
diskArgs.pfd.close();
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Error attempting to close PFD : " + ioe);
}
}
diskArgs.pfd = null;
map = new JSONObject(jsonString);
boolean inserted = map.getBoolean("inserted");
if (!inserted) {
ejectDisk(isDriveA);
}
} catch (Throwable t) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "OOPS: " + t);
}
}
public static void ejectDisk(boolean isDriveA) {
if (isDriveA) {
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_A, "");
} else {
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_PATH_B, "");
}
nativeEjectDisk(isDriveA);
}
public void showDiskInsertionAlertDialog(String title, final DiskArgs diskArgs) {
title = mActivity.getResources().getString(R.string.header_disks) + " " + title;
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity).setCancelable(true).setMessage(title);
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View diskConfirmationView = inflater.inflate(R.layout.a2disk_confirmation, null, false);
builder.setView(diskConfirmationView);
final RadioButton driveA = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskA);
boolean driveAChecked = (boolean) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DRIVE_A);
driveA.setChecked(driveAChecked);
driveA.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DRIVE_A, isChecked);
}
});
final RadioButton driveB = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskB);
driveB.setChecked(!driveAChecked);
final RadioButton readOnly = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_readOnly);
boolean roChecked = (boolean) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_RO_BUTTON);
readOnly.setChecked(roChecked);
readOnly.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref(SETTINGS.CURRENT_DISK_RO_BUTTON, isChecked);
}
});
final RadioButton readWrite = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_readWrite);
readWrite.setChecked(!roChecked);
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean isDriveA = driveA.isChecked();
boolean diskReadOnly = readOnly.isChecked();
insertDisk(mActivity, diskArgs, isDriveA, diskReadOnly, /*onLaunch:*/false);
dialog.dismiss();
mActivity.dismissAllMenus();
}
});
AlertDialog dialog = builder.create();
mActivity.registerAndShowDialog(dialog);
}
public static boolean hasDiskExtension(String name) {
// check file extensions ... sigh ... no String.endsWithIgnoreCase() ?
@@ -336,6 +486,20 @@ public class Apple2DisksMenu implements Apple2MenuView {
return (suffix.equalsIgnoreCase(".dsk.gz") || suffix.equalsIgnoreCase(".nib.gz"));
}
public static boolean hasStateExtension(String name) {
// check file extensions ... sigh ... no String.endsWithIgnoreCase() ?
final int extLen = Apple2MainMenu.SAVE_FILE_EXTENSION.length();
final int len = name.length();
if (len <= extLen) {
return false;
}
final String suffix = name.substring(len - extLen, len);
return suffix.equalsIgnoreCase(Apple2MainMenu.SAVE_FILE_EXTENSION);
}
// ------------------------------------------------------------------------
// internals ...
@@ -351,128 +515,103 @@ public class Apple2DisksMenu implements Apple2MenuView {
return pathBuffer.toString();
}
private static void recursivelyDelete(File file) {
if (file.isDirectory()) {
for (File f : file.listFiles()) {
recursivelyDelete(f);
}
}
if (!file.delete()) {
Log.d(TAG, "Failed to delete file: " + file);
}
}
private static void recursivelyCopyAPKAssets(Apple2Activity activity, String srcFileOrDir, String dstFileOrDir) {
AssetManager assetManager = activity.getAssets();
final int maxAttempts = 5;
String[] files = null;
int attempts = 0;
do {
try {
files = assetManager.list(srcFileOrDir);
break;
} catch (InterruptedIOException e) {
/* EINTR, EAGAIN ... */
} catch (IOException e) {
Log.d(TAG, "OOPS exception attempting to list APK files at : " + srcFileOrDir + " : " + e);
}
try {
Thread.sleep(100, 0);
} catch (InterruptedException ie) {
/* ... */
}
++attempts;
} while (attempts < maxAttempts);
if (files == null) {
Log.d(TAG, "OOPS, could not list APK assets at : " + srcFileOrDir);
return;
}
if (files.length > 0) {
// ensure destination directory exists
File dstPath = new File(dstFileOrDir);
if (!dstPath.mkdirs()) {
if (!dstPath.exists()) {
Log.d(TAG, "OOPS, could not mkdirs on " + dstPath);
return;
}
}
for (String filename : files) {
// iterate on files and subdirectories
recursivelyCopyAPKAssets(activity, srcFileOrDir + File.separator + filename, dstFileOrDir + File.separator + filename);
}
return;
}
// presumably this is a file, not a subdirectory
InputStream is = null;
FileOutputStream os = null;
attempts = 0;
do {
try {
is = assetManager.open(srcFileOrDir);
os = new FileOutputStream(dstFileOrDir);
copyFile(is, os);
break;
} catch (InterruptedIOException e) {
/* EINTR, EAGAIN */
} catch (IOException e) {
Log.e(TAG, "Failed to copy asset file: " + srcFileOrDir, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
// NOOP
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
// NOOP
}
}
}
try {
Thread.sleep(100, 0);
} catch (InterruptedException ie) {
/* ... */
}
++attempts;
} while (attempts < maxAttempts);
}
private static void copyFile(InputStream is, FileOutputStream os) throws IOException {
final int BUF_SZ = 4096;
byte[] buf = new byte[BUF_SZ];
while (true) {
int len = is.read(buf, 0, BUF_SZ);
if (len < 0) {
break;
}
os.write(buf, 0, len);
}
os.flush();
}
private void dynamicSetup() {
final ListView disksList = (ListView) mDisksView.findViewById(R.id.listView_settings);
disksList.setEnabled(true);
final boolean useNewschoolSelection = (boolean) Apple2Preferences.getJSONPref(SETTINGS.USE_NEWSCHOOL_DISK_SELECTION);
final CheckBox newschoolSelection = (CheckBox) mDisksView.findViewById(R.id.newschoolDiskSelectionButton);
newschoolSelection.setChecked(useNewschoolSelection);
final boolean isKitKatOrBetter = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT);
final boolean includeExternalFileChooser = Apple2Utils.isExternalStorageAccessible(mActivity) && isKitKatOrBetter;
final View newschoolChooser = mDisksView.findViewById(R.id.disk_selection_newschool_chooser);
if (!includeExternalFileChooser || !useNewschoolSelection) {
disksList.setEnabled(true);
disksList.setVisibility(View.VISIBLE);
newschoolChooser.setVisibility(View.INVISIBLE);
for (int i = 0; i < 2; i++) {
LinearLayout layout = (LinearLayout) mDisksView.findViewById((i == 0) ? R.id.a2_newschool_driveA_layout : R.id.a2_newschool_driveB_layout);
layout.setVisibility(View.INVISIBLE);
}
if (!includeExternalFileChooser) {
newschoolSelection.setVisibility(View.INVISIBLE);
}
Apple2Preferences.setJSONPref(SETTINGS.USE_NEWSCHOOL_DISK_SELECTION, false);
oldschoolDynamicSetup();
return;
}
disksList.setEnabled(false);
disksList.setVisibility(View.INVISIBLE);
newschoolChooser.setVisibility(View.VISIBLE);
// new external file chooser activity can allow navigation to restricted external SD Card(s) ...
newschoolSelection.setVisibility(View.VISIBLE);
for (int i = 0; i < 2; i++) {
String imageName = null;
do {
String diskPath = (String) Apple2Preferences.getJSONPref((i == 0) ? SETTINGS.CURRENT_DISK_PATH_A : SETTINGS.CURRENT_DISK_PATH_B);
if (diskPath == null || diskPath.equals("")) {
break;
}
if (diskPath.startsWith(Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL)) {
diskPath = diskPath.substring(Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL.length());
}
Uri uri = Uri.parse(diskPath);
if (uri == null) {
break;
}
diskPath = uri.getPath();
int idx = diskPath.lastIndexOf("/");
if (idx < 0) {
break;
}
imageName = Apple2DiskChooserActivity.getFileNameFromUri(mActivity, uri);
} while (false);
LinearLayout layout = (LinearLayout) mDisksView.findViewById((i == 0) ? R.id.a2_newschool_driveA_layout : R.id.a2_newschool_driveB_layout);
if (imageName == null || imageName.equals("")) {
layout.setVisibility(View.INVISIBLE);
} else {
layout.setVisibility(View.VISIBLE);
boolean readOnly = (boolean) Apple2Preferences.getJSONPref((i == 0) ? SETTINGS.CURRENT_DISK_PATH_A_RO : SETTINGS.CURRENT_DISK_PATH_B_RO);
imageName = "(" + mActivity.getResources().getString((readOnly ? R.string.disk_read_only : R.string.disk_read_write)) + "): " + imageName;
TextView textView = (TextView) mDisksView.findViewById((i == 0) ? R.id.a2_newschool_diskA : R.id.a2_newschool_diskB);
textView.setText(imageName);
}
}
}
private void oldschoolDynamicSetup() {
final ListView disksList = (ListView) mDisksView.findViewById(R.id.listView_settings);
String disksDir = pathStackAsDirectory();
boolean isRootPath = false;
if (disksDir == null) {
isRootPath = true;
disksDir = sDataDir + File.separator + "disks"; // default path
final boolean isRootPath = (disksDir == null);
if (isRootPath) {
disksDir = Apple2Utils.getDataDir(mActivity) + File.separator + "disks"; // default path
}
while (disksDir.charAt(disksDir.length() - 1) == File.separatorChar) {
disksDir = disksDir.substring(0, disksDir.length() - 1);
}
File dir = new File(disksDir);
final File[] files = dir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
name = name.toLowerCase();
@@ -483,7 +622,7 @@ public class Apple2DisksMenu implements Apple2MenuView {
return false;
}
File file = new File(dir, name);
return file.isDirectory() || hasDiskExtension(name);
return file.isDirectory() || hasDiskExtension(name) || hasStateExtension(name);
}
});
@@ -497,28 +636,41 @@ public class Apple2DisksMenu implements Apple2MenuView {
Arrays.sort(files);
getExternalStorageDirectory(mActivity);
final boolean includeExternalStoragePath = (sExternalFilesDir != null && isRootPath);
final boolean includeDownloadsPath = (sDownloadFilesDir != null && isRootPath);
final int offset = includeExternalStoragePath ? (includeDownloadsPath ? 2 : 1) : (includeDownloadsPath ? 1 : 0);
final File realExtStorageDir = Apple2Utils.getRealExternalStorageDirectory(mActivity);
final boolean isStoragePath = !isRootPath && (realExtStorageDir != null) && disksDir.equalsIgnoreCase(realExtStorageDir.getAbsolutePath());
if (isStoragePath) {
// promote apple2ix directory to top of list
for (int i = 0; i < files.length; i++) {
if (files[i].getName().equals("apple2ix")) {
if (i > 0) {
System.arraycopy(/*src:*/files, /*srcPos:*/0, /*dst:*/files, /*dstPos:*/1, /*length:*/i);
files[0] = new File(realExtStorageDir, "apple2ix");
}
break;
}
}
}
final boolean includeExternalStoragePath = (Apple2Utils.isExternalStorageAccessible(mActivity) && isRootPath);
final int offset = includeExternalStoragePath ? 1 : 0;
final String[] fileNames = new String[files.length + offset];
final String[] filePaths = new String[files.length + offset];
final boolean[] isDirectory = new boolean[files.length + offset];
int idx = 0;
if (includeExternalStoragePath) {
fileNames[idx] = sExternalFilesDir.getAbsolutePath();
isDirectory[idx] = true;
++idx;
}
if (includeDownloadsPath) {
fileNames[idx] = sDownloadFilesDir.getAbsolutePath();
filePaths[idx] = Apple2Utils.getRealExternalStorageDirectory(mActivity).getAbsolutePath();
fileNames[idx] = mActivity.getResources().getString(R.string.storage);
isDirectory[idx] = true;
++idx;
}
for (File file : files) {
isDirectory[idx] = file.isDirectory();
fileNames[idx] = file.getName();
filePaths[idx] = file.getName();
fileNames[idx] = filePaths[idx];
if (isDirectory[idx]) {
fileNames[idx] += File.separator;
}
@@ -549,33 +701,25 @@ public class Apple2DisksMenu implements Apple2MenuView {
} else {
String imageName = files[position - offset].getAbsolutePath();
final int len = imageName.length();
final String suffix = imageName.substring(len - 3, len);
if (suffix.equalsIgnoreCase(".gz")) {
imageName = files[position - offset].getAbsolutePath().substring(0, len - 3);
}
String eject = mActivity.getResources().getString(R.string.disk_eject);
if (imageName.equals(Apple2Preferences.CURRENT_DISK_A.stringValue(mActivity))) {
if (imageName.equals((String) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_A))) {
Button ejectButton = new Button(mActivity);
ejectButton.setText(eject + " 1");
ejectButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mActivity.nativeEjectDisk(/*driveA:*/true);
Apple2Preferences.CURRENT_DISK_A.saveString(mActivity, "");
ejectDisk(/*driveA:*/true);
dynamicSetup();
}
});
layout.addView(ejectButton);
} else if (imageName.equals(Apple2Preferences.CURRENT_DISK_B.stringValue(mActivity))) {
} else if (imageName.equals((String) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_B))) {
Button ejectButton = new Button(mActivity);
ejectButton.setText(eject + " 2");
ejectButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mActivity.nativeEjectDisk(/*driveA:*/false);
Apple2Preferences.CURRENT_DISK_B.saveString(mActivity, "");
ejectDisk(/*driveA:*/false);
dynamicSetup();
}
});
@@ -595,11 +739,11 @@ public class Apple2DisksMenu implements Apple2MenuView {
@Override
public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
if (isDirectory[position]) {
Log.d(TAG, "Descending to path : " + fileNames[position]);
if (parentIsRootPath && !new File(fileNames[position]).isAbsolute()) {
pushPathStack(parentDisksDir + File.separator + fileNames[position]);
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Descending to path : " + filePaths[position]);
if (parentIsRootPath && !new File(filePaths[position]).isAbsolute()) {
pushPathStack(parentDisksDir + File.separator + filePaths[position]);
} else {
pushPathStack(fileNames[position]);
pushPathStack(filePaths[position]);
}
dynamicSetup();
ListView disksList = (ListView) mDisksView.findViewById(R.id.listView_settings);
@@ -607,85 +751,29 @@ public class Apple2DisksMenu implements Apple2MenuView {
return;
}
String str = files[position - offset].getAbsolutePath();
final int len = str.length();
final String suffix = str.substring(len - 3, len);
if (suffix.equalsIgnoreCase(".gz")) {
str = files[position - offset].getAbsolutePath().substring(0, len - 3);
}
final String imageName = str;
if (imageName.equals(Apple2Preferences.CURRENT_DISK_A.stringValue(mActivity))) {
mActivity.nativeEjectDisk(/*driveA:*/true);
Apple2Preferences.CURRENT_DISK_A.saveString(mActivity, "");
final String imageName = files[position - offset].getAbsolutePath();
if (imageName.equals((String) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_A))) {
ejectDisk(/*isDriveA:*/true);
dynamicSetup();
return;
}
if (imageName.equals(Apple2Preferences.CURRENT_DISK_B.stringValue(mActivity))) {
mActivity.nativeEjectDisk(/*driveA:*/false);
Apple2Preferences.CURRENT_DISK_B.saveString(mActivity, "");
if (imageName.equals((String) Apple2Preferences.getJSONPref(SETTINGS.CURRENT_DISK_PATH_B))) {
ejectDisk(/*isDriveA:*/false);
dynamicSetup();
return;
}
String title = mActivity.getResources().getString(R.string.header_disks);
title = title + " " + fileNames[position];
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity).setCancelable(true).setMessage(title);
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View diskConfirmationView = inflater.inflate(R.layout.a2disk_confirmation, null, false);
builder.setView(diskConfirmationView);
final RadioButton diskA = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskA);
diskA.setChecked(Apple2Preferences.CURRENT_DRIVE_A_BUTTON.booleanValue(mActivity));
diskA.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.CURRENT_DRIVE_A_BUTTON.saveBoolean(mActivity, isChecked);
if (hasStateExtension(imageName)) {
final String jsonString = "{ \"stateFile\" : \"" + imageName + "\" }";
final boolean restored = Apple2MainMenu.restoreEmulatorState(mActivity, jsonString);
mActivity.dismissAllMenus();
if (!restored) {
Toast.makeText(mActivity, R.string.state_not_restored, Toast.LENGTH_SHORT).show();
}
});
final RadioButton diskB = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskB);
diskB.setChecked(!Apple2Preferences.CURRENT_DRIVE_A_BUTTON.booleanValue(mActivity));
return;
}
final RadioButton readOnly = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_readOnly);
readOnly.setChecked(Apple2Preferences.CURRENT_DISK_RO_BUTTON.booleanValue(mActivity));
readOnly.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.CURRENT_DISK_RO_BUTTON.saveBoolean(mActivity, isChecked);
}
});
final RadioButton readWrite = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_readWrite);
readWrite.setChecked(!Apple2Preferences.CURRENT_DISK_RO_BUTTON.booleanValue(mActivity));
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean isDriveA = diskA.isChecked();
boolean diskReadOnly = readOnly.isChecked();
if (isDriveA) {
Apple2Preferences.CURRENT_DISK_A_RO.saveBoolean(mActivity, diskReadOnly);
Apple2Preferences.CURRENT_DISK_A.saveString(mActivity, imageName);
} else {
Apple2Preferences.CURRENT_DISK_B_RO.saveBoolean(mActivity, diskReadOnly);
Apple2Preferences.CURRENT_DISK_B.saveString(mActivity, imageName);
}
dialog.dismiss();
mActivity.dismissAllMenus();
}
});
AlertDialog dialog = builder.create();
mActivity.registerAndShowDialog(dialog);
showDiskInsertionAlertDialog(/*title:*/fileNames[position], /*diskPath:*/new DiskArgs(imageName));
}
});
}

View File

@@ -0,0 +1,143 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2019 Aaron Culliney
*
*/
package org.deadc0de.apple2ix;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.atomic.AtomicBoolean;
public class Apple2EmailerActivity extends AppCompatActivity {
public static final String EXTRA_STREAM_PATH = Intent.EXTRA_STREAM + "-PATH";
public static final int SEND_REQUEST_CODE = 46;
public static final AtomicBoolean sEmailerIsEmailing = new AtomicBoolean(false);
public static Callback sEmailerCallback;
public interface Callback {
void onEmailerFinished();
}
@Override
protected void onRestoreInstanceState(Bundle inState) {
super.onRestoreInstanceState(inState);
mExtraText = inState.getString(Intent.EXTRA_TEXT);
mExtraStreamPath = inState.getString(EXTRA_STREAM_PATH);
mExtraStream = inState.getParcelable(Intent.EXTRA_STREAM);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putBoolean("ran", true);
outState.putString(Intent.EXTRA_TEXT, mExtraText);
outState.putString(EXTRA_STREAM_PATH, mExtraStreamPath);
outState.putParcelable(Intent.EXTRA_STREAM, mExtraStream);
super.onSaveInstanceState(outState);
}
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
Bundle b;
{
Intent intent = getIntent();
Bundle extras = null;
if (intent != null) {
extras = intent.getExtras();
}
if (savedState != null) {
b = savedState;
} else if (extras != null) {
b = extras;
} else {
b = new Bundle();
}
}
final boolean ran = b.getBoolean("ran");
mExtraText = b.getString(Intent.EXTRA_TEXT);
mExtraStreamPath = b.getString(EXTRA_STREAM_PATH);
MediaScannerConnection.scanFile(this,
/*paths:*/new String[] { mExtraStreamPath },
/*mimeTypes:*/new String[] { "application/zip" },
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
mExtraStream = uri;
Intent emailIntent = new Intent(Intent.ACTION_SEND);
try {
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { "apple2ix_crash@deadcode.org" });
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "A2IX Report To apple2ix_crash@deadcode.org");
emailIntent.putExtra(Intent.EXTRA_TEXT, mExtraText);
emailIntent.putExtra(Intent.EXTRA_STREAM, mExtraStream);
emailIntent.setType("message/rfc822");
if (!ran) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "STARTING EMAIL ACTIVITY ...");
startActivityForResult(emailIntent, SEND_REQUEST_CODE);
}
} catch (Throwable t) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS : " + t);
setResult(RESULT_CANCELED);
finish();
}
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_CANCELED) {
// Do something?
}
if (mExtraStream != null) {
int deleted = getContentResolver().delete(mExtraStream, null, null);
}
sEmailerCallback.onEmailerFinished();
setResult(RESULT_OK);
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void finish() {
sEmailerIsEmailing.set(false);
super.finish();
}
private String mExtraText;
private String mExtraStreamPath;
private Uri mExtraStream;
private static final String TAG = "A2EmailerActivity";
}

View File

@@ -12,9 +12,11 @@
package org.deadc0de.apple2ix;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
import org.deadc0de.apple2ix.basic.R;
@@ -22,25 +24,57 @@ import java.util.ArrayList;
public class Apple2JoystickCalibration implements Apple2MenuView {
private final static String TAG = "Apple2JoystickCalibration";
private final static String TAG = "apple2ix-js-calibrate";
public final static int JOYSTICK_DIVIDER_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES;
public final static String PREF_SCREEN_DIVISION = "screenDivider";
private static native long nativePollJoystick();
private Apple2Activity mActivity = null;
private View mSettingsView = null;
private ArrayList<Apple2MenuView> mViewStack = null;
private boolean mTouchMenuEnabled = false;
private int mSavedTouchDevice = Apple2Preferences.TouchDeviceVariant.NONE.ordinal();
private int mSavedTouchDevice = Apple2SettingsMenu.TouchDeviceVariant.NONE.ordinal();
private Thread joystickPollerThread = null;
public Apple2JoystickCalibration(Apple2Activity activity, ArrayList<Apple2MenuView> viewStack, Apple2Preferences.TouchDeviceVariant variant) {
public static void startCalibration(Apple2Activity activity, Apple2SettingsMenu.TouchDeviceVariant variant) {
ArrayList<Apple2MenuView> viewStack = new ArrayList<Apple2MenuView>();
{
int idx = 0;
while (true) {
Apple2MenuView apple2MenuView = activity.peekApple2View(idx);
if (apple2MenuView == null) {
break;
}
viewStack.add(apple2MenuView);
++idx;
}
}
Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, variant);
// show this new view...
calibration.show();
// ...with nothing else underneath 'cept the emulator OpenGL layer
for (Apple2MenuView apple2MenuView : viewStack) {
activity.popApple2View(apple2MenuView);
}
}
private Apple2JoystickCalibration(Apple2Activity activity, ArrayList<Apple2MenuView> viewStack, Apple2SettingsMenu.TouchDeviceVariant variant) {
mActivity = activity;
mViewStack = viewStack;
if (!(variant == Apple2Preferences.TouchDeviceVariant.JOYSTICK || variant == Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD)) {
if (!(variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK || variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD)) {
throw new RuntimeException("You're doing it wrong");
}
setup(variant);
}
private void setup(Apple2Preferences.TouchDeviceVariant variant) {
private void setup(final Apple2SettingsMenu.TouchDeviceVariant variant) {
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mSettingsView = inflater.inflate(R.layout.activity_calibrate_joystick, null, false);
@@ -51,7 +85,8 @@ public class Apple2JoystickCalibration implements Apple2MenuView {
if (!fromUser) {
return;
}
Apple2Preferences.JOYSTICK_DIVIDER.saveInt(mActivity, progress);
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_SCREEN_DIVISION, (float) progress / JOYSTICK_DIVIDER_NUM_CHOICES);
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_JOYSTICK);
}
@Override
@@ -64,20 +99,59 @@ public class Apple2JoystickCalibration implements Apple2MenuView {
});
sb.setMax(0); // http://stackoverflow.com/questions/10278467/seekbar-not-setting-actual-progress-setprogress-not-working-on-early-android
sb.setMax(Apple2Preferences.JOYSTICK_DIVIDER_NUM_CHOICES);
sb.setProgress(Apple2Preferences.JOYSTICK_DIVIDER.intValue(mActivity));
sb.setMax(JOYSTICK_DIVIDER_NUM_CHOICES);
float val = Apple2Preferences.getFloatJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_SCREEN_DIVISION, (JOYSTICK_DIVIDER_NUM_CHOICES >> 1) / (float) JOYSTICK_DIVIDER_NUM_CHOICES);
sb.setProgress((int) (val * JOYSTICK_DIVIDER_NUM_CHOICES));
mTouchMenuEnabled = Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(mActivity);
Apple2Preferences.nativeSetTouchMenuEnabled(false);
mSavedTouchDevice = Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(mActivity);
Apple2Preferences.nativeSetCurrentTouchDevice(variant.ordinal());
if (variant == Apple2Preferences.TouchDeviceVariant.JOYSTICK) {
Apple2Preferences.loadAllJoystickButtons(mActivity);
} else {
Apple2Preferences.loadAllKeypadKeys(mActivity);
}
final TextView axisCoords = (TextView) mSettingsView.findViewById(R.id.axisCoords);
Apple2Preferences.nativeTouchDeviceBeginCalibrationMode();
joystickPollerThread = new Thread() {
@Override
public void run() {
Apple2Activity.logMessage(Apple2Activity.LogType.INFO, TAG, "Starting joystick poll thread...");
try {
while (true) {
long cxy = nativePollJoystick();
String t = "";
if (variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK) {
final int x = ((int) (cxy & 0xFF00) >> 8) - 128;
final int y = ((int) (cxy & 0x00FF) >> 0) - 128;
t = "X: " + x + " Y: " + y;
} else {
char ascii = (char) ((cxy & 0xFF000000) >> 24);
int scancode = (char) ((cxy & 0x00FF0000) >> 16);
if (ascii == Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION || scancode == 0) {
// ...
} else {
t = "Key: " + Apple2KeypadChooser.asciiRepresentation(mActivity, ascii);
}
}
final String axisText = t;
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
axisCoords.setText(axisText);
}
});
Thread.sleep(100);
}
} catch (Exception e) {
Apple2Activity.logMessage(Apple2Activity.LogType.INFO, TAG, "Stopping joystick poll thread...");
}
}
};
joystickPollerThread.start();
mTouchMenuEnabled = (boolean) Apple2Preferences.getJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED);
Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, false);
mSavedTouchDevice = (int) Apple2Preferences.getJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT);
Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, variant.ordinal());
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, true);
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN);
}
public final boolean isCalibrating() {
@@ -85,7 +159,6 @@ public class Apple2JoystickCalibration implements Apple2MenuView {
}
public void onKeyTapCalibrationEvent(char ascii, int scancode) {
/* ... */
}
public void show() {
@@ -96,15 +169,18 @@ public class Apple2JoystickCalibration implements Apple2MenuView {
}
public void dismiss() {
joystickPollerThread.interrupt();
for (Apple2MenuView apple2MenuView : mViewStack) {
if (apple2MenuView != this) {
mActivity.pushApple2View(apple2MenuView);
}
}
Apple2Preferences.nativeTouchDeviceEndCalibrationMode();
Apple2Preferences.nativeSetTouchMenuEnabled(mTouchMenuEnabled);
Apple2Preferences.nativeSetCurrentTouchDevice(mSavedTouchDevice);
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, false);
Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, mTouchMenuEnabled);
Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, mSavedTouchDevice);
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN);
mActivity.popApple2View(this);
}

View File

@@ -11,6 +11,7 @@
package org.deadc0de.apple2ix;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
@@ -24,8 +25,29 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
private final static String TAG = "Apple2JoystickSettingsMenu";
public Apple2JoystickSettingsMenu(Apple2Activity activity) {
public final static int JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES;
public final static float JOYSTICK_AXIS_SENSITIVITY_MIN = 0.25f;
public final static float JOYSTICK_AXIS_SENSITIVITY_DEFAULT = 1.f;
public final static float JOYSTICK_AXIS_SENSITIVITY_MAX = 4.f;
public final static float JOYSTICK_AXIS_SENSITIVITY_DEC_STEP = 0.05f;
public final static float JOYSTICK_AXIS_SENSITIVITY_INC_STEP = 0.25f;
public final static int JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES = (int) ((JOYSTICK_AXIS_SENSITIVITY_DEFAULT - JOYSTICK_AXIS_SENSITIVITY_MIN) / JOYSTICK_AXIS_SENSITIVITY_DEC_STEP); // 15
public final static int JOYSTICK_AXIS_SENSITIVITY_INC_NUMCHOICES = (int) ((JOYSTICK_AXIS_SENSITIVITY_MAX - JOYSTICK_AXIS_SENSITIVITY_DEFAULT) / JOYSTICK_AXIS_SENSITIVITY_INC_STEP); // 12
public final static int JOYSTICK_AXIS_SENSITIVITY_NUM_CHOICES = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES + JOYSTICK_AXIS_SENSITIVITY_INC_NUMCHOICES; // 15 + 12
public final static int TAPDELAY_NUM_CHOICES = (30 + 1); // 0-30 (30Frames == ~0.5sec)
private Apple2SettingsMenu.TouchDeviceVariant mVariant;
public Apple2JoystickSettingsMenu(Apple2Activity activity, Apple2SettingsMenu.TouchDeviceVariant variant) {
super(activity);
if (!(variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK || variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD)) {
throw new RuntimeException("You're doing it wrong");
}
mVariant = variant;
}
@Override
@@ -51,7 +73,46 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
return true;
}
public enum TouchJoystickButtons {
NONE(0),
BUTTON1(1),
BUTTON2(2),
BOTH(3);
private int butt;
TouchJoystickButtons(int butt) {
this.butt = butt;
}
}
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
JOYSTICK_CALIBRATE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_calibrate);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_calibrate_summary);
}
@Override
public String getPrefKey() {
return null;
}
@Override
public Object getPrefDefault() {
return null;
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
Apple2JoystickSettingsMenu thisMenu = (Apple2JoystickSettingsMenu)settingsMenu;
Apple2JoystickCalibration.startCalibration(activity, thisMenu.mVariant);
}
},
JOYSTICK_TAP_BUTTON {
@Override
public final String getTitle(Apple2Activity activity) {
@@ -63,6 +124,16 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.joystick_button_tap_button_summary);
}
@Override
public String getPrefKey() {
return "jsTouchDownChar";
}
@Override
public Object getPrefDefault() {
return TouchJoystickButtons.BUTTON1.ordinal();
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
@@ -72,6 +143,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
final IMenuEnum self = this;
_alertDialogHandleSelection(activity, R.string.joystick_button_tap_button, new String[]{
activity.getResources().getString(R.string.joystick_button_button_none),
activity.getResources().getString(R.string.joystick_button_button1),
@@ -80,12 +152,12 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
return Apple2Preferences.JOYSTICK_TAP_BUTTON.intValue(activity);
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
public void saveInt(int value) {
Apple2Preferences.JOYSTICK_TAP_BUTTON.saveTouchJoystickButtons(activity, Apple2Preferences.TouchJoystickButtons.values()[value]);
Apple2Preferences.setJSONPref(self, TouchJoystickButtons.values()[value].ordinal());
}
});
}
@@ -101,6 +173,16 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.joystick_button_swipe_up_button_summary);
}
@Override
public String getPrefKey() {
return "jsSwipeNorthChar";
}
@Override
public Object getPrefDefault() {
return TouchJoystickButtons.BOTH.ordinal();
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
@@ -110,6 +192,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
final IMenuEnum self = this;
_alertDialogHandleSelection(activity, R.string.joystick_button_swipe_up_button, new String[]{
activity.getResources().getString(R.string.joystick_button_button_none),
activity.getResources().getString(R.string.joystick_button_button1),
@@ -118,12 +201,12 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
return Apple2Preferences.JOYSTICK_SWIPEUP_BUTTON.intValue(activity);
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
public void saveInt(int value) {
Apple2Preferences.JOYSTICK_SWIPEUP_BUTTON.saveTouchJoystickButtons(activity, Apple2Preferences.TouchJoystickButtons.values()[value]);
Apple2Preferences.setJSONPref(self, TouchJoystickButtons.values()[value].ordinal());
}
});
}
@@ -139,6 +222,16 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.joystick_button_swipe_down_button_summary);
}
@Override
public String getPrefKey() {
return "jsSwipeSouthChar";
}
@Override
public Object getPrefDefault() {
return TouchJoystickButtons.BUTTON2.ordinal();
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
@@ -148,6 +241,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
final IMenuEnum self = this;
_alertDialogHandleSelection(activity, R.string.joystick_button_swipe_down_button, new String[]{
activity.getResources().getString(R.string.joystick_button_button_none),
activity.getResources().getString(R.string.joystick_button_button1),
@@ -156,54 +250,115 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
return Apple2Preferences.JOYSTICK_SWIPEDOWN_BUTTON.intValue(activity);
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
public void saveInt(int value) {
Apple2Preferences.JOYSTICK_SWIPEDOWN_BUTTON.saveTouchJoystickButtons(activity, Apple2Preferences.TouchJoystickButtons.values()[value]);
Apple2Preferences.setJSONPref(self, TouchJoystickButtons.values()[value].ordinal());
}
});
}
},
JOYSTICK_CALIBRATE {
JOYSTICK_SWIPELEFT_BUTTON {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_calibrate);
return activity.getResources().getString(R.string.joystick_button_swipe_left_button);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_calibrate_summary);
return activity.getResources().getString(R.string.joystick_button_swipe_left_button_summary);
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
ArrayList<Apple2MenuView> viewStack = new ArrayList<Apple2MenuView>();
{
int idx = 0;
while (true) {
Apple2MenuView apple2MenuView = activity.peekApple2View(idx);
if (apple2MenuView == null) {
break;
}
viewStack.add(apple2MenuView);
++idx;
public String getPrefKey() {
return "jsSwipeWestChar";
}
@Override
public Object getPrefDefault() {
return TouchJoystickButtons.NONE.ordinal();
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
_addPopupIcon(activity, this, convertView);
return convertView;
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
final IMenuEnum self = this;
_alertDialogHandleSelection(activity, R.string.joystick_button_swipe_left_button, new String[]{
activity.getResources().getString(R.string.joystick_button_button_none),
activity.getResources().getString(R.string.joystick_button_button1),
activity.getResources().getString(R.string.joystick_button_button2),
activity.getResources().getString(R.string.joystick_button_button_both),
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
return (int) Apple2Preferences.getJSONPref(self);
}
}
Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2Preferences.TouchDeviceVariant.JOYSTICK);
// show this new view...
calibration.show();
// ...with nothing else underneath 'cept the emulator OpenGL layer
for (Apple2MenuView apple2MenuView : viewStack) {
activity.popApple2View(apple2MenuView);
}
@Override
public void saveInt(int value) {
Apple2Preferences.setJSONPref(self, TouchJoystickButtons.values()[value].ordinal());
}
});
}
},
JOYSTICK_TAPDELAY {
JOYSTICK_SWIPERIGHT_BUTTON {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_button_swipe_right_button);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_button_swipe_right_button_summary);
}
@Override
public String getPrefKey() {
return "jsSwipeEastChar";
}
@Override
public Object getPrefDefault() {
return TouchJoystickButtons.NONE.ordinal();
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
_addPopupIcon(activity, this, convertView);
return convertView;
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
final IMenuEnum self = this;
_alertDialogHandleSelection(activity, R.string.joystick_button_swipe_right_button, new String[]{
activity.getResources().getString(R.string.joystick_button_button_none),
activity.getResources().getString(R.string.joystick_button_button1),
activity.getResources().getString(R.string.joystick_button_button2),
activity.getResources().getString(R.string.joystick_button_button_both),
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
public void saveInt(int value) {
Apple2Preferences.setJSONPref(self, TouchJoystickButtons.values()[value].ordinal());
}
});
}
},
JOYSTICK_AXIS_SENSITIVITY {
@Override
public final String getTitle(Apple2Activity activity) {
return "";
@@ -211,25 +366,55 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_button_tapdelay_summary);
return activity.getResources().getString(R.string.joystick_axis_sensitivity_summary);
}
@Override
public String getPrefKey() {
return "axisSensitivity";
}
@Override
public Object getPrefDefault() {
return 1.f;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.TAPDELAY_NUM_CHOICES, new IPreferenceSlider() {
final IMenuEnum self = this;
return _sliderView(activity, this, JOYSTICK_AXIS_SENSITIVITY_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.JOYSTICK_TAPDELAY.saveInt(activity, progress);
final int pivot = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES;
float sensitivity = 1.f;
if (progress < pivot) {
int decAmount = (pivot - progress);
sensitivity -= (JOYSTICK_AXIS_SENSITIVITY_DEC_STEP * decAmount);
} else if (progress > pivot) {
int incAmount = (progress - pivot);
sensitivity += (JOYSTICK_AXIS_SENSITIVITY_INC_STEP * incAmount);
}
Apple2Preferences.setJSONPref(self, sensitivity);
}
@Override
public int intValue() {
return Apple2Preferences.JOYSTICK_TAPDELAY.intValue(activity);
float sensitivity = Apple2Preferences.getFloatJSONPref(self);
int pivot = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES;
if (sensitivity < 1.f) {
pivot = Math.round((sensitivity - JOYSTICK_AXIS_SENSITIVITY_MIN) / JOYSTICK_AXIS_SENSITIVITY_DEC_STEP);
} else if (sensitivity > 1.f) {
sensitivity -= 1.f;
pivot += Math.round(sensitivity / JOYSTICK_AXIS_SENSITIVITY_INC_STEP);
}
return pivot;
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
seekBarValue.setText("" + (((float) progress / Apple2Preferences.TAPDELAY_NUM_CHOICES) * Apple2Preferences.TAPDELAY_SCALE));
saveInt(progress);
int percent = (int) (Apple2Preferences.getFloatJSONPref(self) * 100.f);
seekBarValue.setText("" + percent + "%");
}
});
}
@@ -253,9 +438,23 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
public static final int size = SETTINGS.values().length;
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_JOYSTICK;
}
@Override
public String getPrefKey() {
return null;
}
@Override
public Object getPrefDefault() {
return null;
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
/* ... */
}
@Override
@@ -301,7 +500,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
if (position < 0 || position >= SETTINGS.size) {
throw new ArrayIndexOutOfBoundsException();
}
return position == SETTINGS.JOYSTICK_AXIS_ON_LEFT.ordinal();
return position <= SETTINGS.JOYSTICK_AXIS_ON_LEFT.ordinal();
}
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
@@ -316,14 +515,25 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.joystick_visible_summary);
}
@Override
public String getPrefKey() {
return "showControls";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.JOYSTICK_VISIBILITY.booleanValue(activity));
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.JOYSTICK_VISIBILITY.saveBoolean(activity, isChecked);
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
@@ -340,20 +550,31 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.joystick_axisleft_summary);
}
@Override
public String getPrefKey() {
return "axisIsOnLeft";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.JOYSTICK_AXIS_ON_LEFT.booleanValue(activity));
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.JOYSTICK_AXIS_ON_LEFT.saveBoolean(activity, isChecked);
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
}
},
JOYSTICK_AXIS_SENSITIVIY {
JOYSTICK_TAPDELAY {
@Override
public final String getTitle(Apple2Activity activity) {
return "";
@@ -361,27 +582,40 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_axis_sensitivity_summary);
return activity.getResources().getString(R.string.joystick_button_tapdelay_summary);
}
@Override
public String getPrefKey() {
return "jsTapDelayFrames";
}
@Override
public Object getPrefDefault() {
return 12; // 12 * 16.688millis == ~0.2secs
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.JOYSTICK_AXIS_SENSITIVITY_NUM_CHOICES, new IPreferenceSlider() {
final IMenuEnum self = this;
return _sliderView(activity, this, TAPDELAY_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.JOYSTICK_AXIS_SENSITIVIY.saveInt(activity, progress);
Apple2Preferences.setJSONPref(self, progress);
}
@Override
public int intValue() {
return Apple2Preferences.JOYSTICK_AXIS_SENSITIVIY.intValue(activity);
return Apple2Preferences.getIntJSONPref(self);
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
saveInt(progress);
int percent = (int) (Apple2Preferences.JOYSTICK_AXIS_SENSITIVIY.floatValue(activity) * 100.f);
seekBarValue.setText("" + percent + "%");
String millis = String.format(java.util.Locale.ROOT, "%.3f", progress * 16.688f);
String framesStr = activity.getResources().getString(R.string.string_frames);
String millisStr = activity.getResources().getString(R.string.string_millis);
String textSummary = "" + progress + " " + framesStr + " (" + millis + " " + millisStr + ")";
seekBarValue.setText(textSummary);
}
});
}
@@ -397,25 +631,35 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.joystick_button_threshold_summary);
}
@Override
public String getPrefKey() {
return "switchThreshold";
}
@Override
public Object getPrefDefault() {
return 128;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES, new IPreferenceSlider() {
final IMenuEnum self = this;
return _sliderView(activity, this, JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
if (progress == 0) {
progress = 1;
}
Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD.saveInt(activity, progress);
progress *= getJoystickButtonSwitchThresholdScale(activity);
Apple2Preferences.setJSONPref(self, progress);
}
@Override
public int intValue() {
return Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD.intValue(activity);
int progress = (int) Apple2Preferences.getJSONPref(self);
return (progress / getJoystickButtonSwitchThresholdScale(activity));
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
int threshold = progress * Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD_STEP;
int threshold = progress * getJoystickButtonSwitchThresholdScale(activity);
seekBarValue.setText("" + threshold + " pts");
}
});
@@ -424,9 +668,13 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
public static final int size = SETTINGS.values().length;
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_JOYSTICK;
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
/* ... */
}
@Override
@@ -444,4 +692,17 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
}
}
}
public static int getJoystickButtonSwitchThresholdScale(Apple2Activity activity) {
DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
int smallScreenAxis = dm.widthPixels < dm.heightPixels ? dm.widthPixels : dm.heightPixels;
int oneThirdScreenAxis = smallScreenAxis / 3;
// largest switch threshold value is 1/3 small dimension of screen
return oneThirdScreenAxis / JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES;
}
}

View File

@@ -11,6 +11,7 @@
package org.deadc0de.apple2ix;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
@@ -28,6 +29,15 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu {
private final static String TAG = "KeyboardSettingsMenu";
// These settings must match native side
public final static int kLT = 8;
public final static int kTAB = 9;
public final static int kDN = 10;
public final static int kUP = 11;
public final static int kRET = 13;
public final static int kRT = 21;
public final static int kESC = 27;
public final static int kDEL = 127;
public final static int MOUSETEXT_BEGIN = 0x80;
public final static int MOUSETEXT_CLOSEDAPPLE = MOUSETEXT_BEGIN/*+0x00*/;
public final static int MOUSETEXT_OPENAPPLE = MOUSETEXT_BEGIN + 0x01;
@@ -45,7 +55,9 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu {
public final static int ICONTEXT_NONACTION = ICONTEXT_KBD_BEGIN + 0x0C;
public final static int SCANCODE_A = 30;
public final static int SCANCODE_C = 46;
public final static int SCANCODE_D = 32;
public final static int SCANCODE_E = 18;
public final static int SCANCODE_F = 33;
public final static int SCANCODE_H = 35;
public final static int SCANCODE_I = 23;
@@ -55,17 +67,23 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu {
public final static int SCANCODE_M = 50;
public final static int SCANCODE_N = 49;
public final static int SCANCODE_O = 24;
public final static int SCANCODE_P = 25;
public final static int SCANCODE_Q = 16;
public final static int SCANCODE_S = 31;
public final static int SCANCODE_U = 22;
public final static int SCANCODE_W = 17;
public final static int SCANCODE_X = 45;
public final static int SCANCODE_Y = 21;
public final static int SCANCODE_Z = 44;
public final static int SCANCODE_SPACE = 57;
public final static int SCANCODE_SEMICOLON = 39;
public final static int SCANCODE_UP = 103;
public final static int SCANCODE_LEFT = 105;
public final static int SCANCODE_RIGHT = 106;
public final static int SCANCODE_DOWN = 108;
public final static int SCANCODE_COMMA = 51;
public final static int SCANCODE_PERIOD = 52;
public final static int SCANCODE_SLASH = 53;
public Apple2KeyboardSettingsMenu(Apple2Activity activity) {
super(activity);
@@ -91,115 +109,45 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu {
if (position < 0 || position >= SETTINGS.size) {
throw new ArrayIndexOutOfBoundsException();
}
return true;
return (position != SETTINGS.KEYBOARD_VISIBILITY_INACTIVE.ordinal() && position != SETTINGS.KEYBOARD_VISIBILITY_ACTIVE.ordinal());
}
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
KEYBOARD_VISIBILITY_INACTIVE {
TOUCH_MENU_ENABLED {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_visibility_inactive);
return activity.getResources().getString(R.string.touch_menu_enable);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_visibility_inactive_summary);
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.KEYBOARD_VISIBILITY_INACTIVE.saveInt(activity, progress);
}
@Override
public int intValue() {
return Apple2Preferences.KEYBOARD_VISIBILITY_INACTIVE.intValue(activity);
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
seekBarValue.setText("" + ((float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
}
});
}
},
KEYBOARD_VISIBILITY_ACTIVE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_visibility_active);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_visibility_active_summary);
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.KEYBOARD_VISIBILITY_ACTIVE.saveInt(activity, progress);
}
@Override
public int intValue() {
return Apple2Preferences.KEYBOARD_VISIBILITY_ACTIVE.intValue(activity);
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
seekBarValue.setText("" + ((float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
}
});
}
},
KEYBOARD_ENABLE_CLICK {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_click_enabled);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_click_enabled_summary);
return activity.getResources().getString(R.string.touch_menu_enable_summary);
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_KEYBOARD;
}
@Override
public String getPrefKey() {
return "touchMenuEnabled";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.KEYBOARD_CLICK_ENABLED.booleanValue(activity));
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.KEYBOARD_CLICK_ENABLED.saveBoolean(activity, isChecked);
}
});
return convertView;
}
},
KEYBOARD_ENABLE_LOWERCASE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_lowercase_enabled);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_lowercase_enabled_summary);
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.KEYBOARD_LOWERCASE_ENABLED.booleanValue(activity));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.KEYBOARD_LOWERCASE_ENABLED.saveBoolean(activity, isChecked);
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
@@ -216,6 +164,16 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.keyboard_choose_alt_summary);
}
@Override
public String getPrefKey() {
return "altPathIndex";
}
@Override
public Object getPrefDefault() {
return 0;
}
@Override
public final View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
@@ -226,7 +184,7 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
File extKeyboardDir = Apple2DisksMenu.getExternalStorageDirectory(activity);
File extKeyboardDir = Apple2Utils.getExternalStorageDirectory(activity);
FilenameFilter kbdJsonFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
@@ -255,10 +213,10 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu {
}
if (files == null) {
// read keyboard data from /data/data/...
File keyboardDir = new File(Apple2DisksMenu.getDataDir(activity) + File.separator + "keyboards");
File keyboardDir = new File(Apple2Utils.getDataDir(activity) + File.separator + "keyboards");
files = keyboardDir.listFiles(kbdJsonFilter);
if (files == null) {
Log.e(TAG, "OOPS, could not read keyboard data directory");
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS, could not read keyboard data directory");
return;
}
}
@@ -275,27 +233,270 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu {
final String keyboardDirName = extKeyboardDir == null ? "Keyboards" : extKeyboardDir.getPath();
final IMenuEnum self = this;
_alertDialogHandleSelection(activity, keyboardDirName, titles, new IPreferenceLoadSave() {
@Override
public int intValue() {
return Apple2Preferences.KEYBOARD_ALT.intValue(activity);
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
public void saveInt(int value) {
Apple2Preferences.KEYBOARD_ALT.saveInt(activity, value);
Apple2Preferences.setJSONPref(self, value);
String path = allFiles[value].getPath();
Apple2Preferences.KEYBOARD_ALT_PATH.saveString(activity, path);
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_KEYBOARD, "altPath", path);
}
});
}
},
KEYBOARD_VISIBILITY_INACTIVE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_visibility_inactive);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_visibility_inactive_summary);
}
@Override
public String getPrefKey() {
return "minAlpha";
}
@Override
public Object getPrefDefault() {
return (float) 5 / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.setJSONPref(self, (float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES);
}
@Override
public int intValue() {
return Math.round(Apple2Preferences.getFloatJSONPref(self) * (Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
seekBarValue.setText("" + ((float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
}
});
}
},
KEYBOARD_VISIBILITY_ACTIVE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_visibility_active);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_visibility_active_summary);
}
@Override
public String getPrefKey() {
return "maxAlpha";
}
@Override
public Object getPrefDefault() {
return 1.f;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.setJSONPref(self, (float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES);
}
@Override
public int intValue() {
return Math.round(Apple2Preferences.getFloatJSONPref(self) * (Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
seekBarValue.setText("" + ((float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
}
});
}
},
KEYBOARD_ENABLE_CLICK {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_click_enabled);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_click_enabled_summary);
}
@Override
public String getPrefKey() {
return "keyClickEnabled";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
final IMenuEnum self = this;
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
}
},
KEYBOARD_ENABLE_DUO_TOUCH {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_duotouch_enabled);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_duotouch_enabled_summary);
}
@Override
public String getPrefKey() {
return "duoTouchEnabled";
}
@Override
public Object getPrefDefault() {
return false;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
final IMenuEnum self = this;
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
}
},
KEYBOARD_ENABLE_LOWERCASE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_lowercase_enabled);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_lowercase_enabled_summary);
}
@Override
public String getPrefKey() {
return "lowercaseEnabled";
}
@Override
public Object getPrefDefault() {
return false;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
final IMenuEnum self = this;
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
}
},
KEYBOARD_GLYPH_SCALE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_glyph_scale);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keyboard_glyph_scale_summary);
}
@Override
public String getPrefKey() {
return "glyphMultiplier";
}
@Override
public Object getPrefDefault() {
return 2;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
int glyphScale = (int) Apple2Preferences.getJSONPref(this);
if (glyphScale <= 0) {
glyphScale = 1;
}
CheckBox cb = _addCheckbox(activity, this, convertView, glyphScale > 1);
final IMenuEnum self = this;
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref(self, isChecked ? 2 : 1);
}
});
return convertView;
}
};
public static final int size = SETTINGS.values().length;
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_KEYBOARD;
}
@Override
public String getPrefKey() {
return null;
}
@Override
public Object getPrefDefault() {
return null;
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
/* ... */
}
@Override

View File

@@ -15,7 +15,6 @@ import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
@@ -23,6 +22,9 @@ import android.widget.TextView;
import java.util.ArrayList;
import org.deadc0de.apple2ix.basic.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Apple2KeypadChooser implements Apple2MenuView {
@@ -33,12 +35,10 @@ public class Apple2KeypadChooser implements Apple2MenuView {
private ArrayList<Apple2MenuView> mViewStack = null;
private TextView mCurrentChoicePrompt = null;
private String[] foo = null;
private STATE_MACHINE mChooserState = STATE_MACHINE.CHOOSE_NORTHWEST;
private STATE_MACHINE mChooserState = STATE_MACHINE.CHOOSE_AXIS_NORTHWEST;
private boolean mTouchMenuEnabled = false;
private int mSavedTouchDevice = Apple2Preferences.TouchDeviceVariant.NONE.ordinal();
private int mSavedTouchDevice = Apple2SettingsMenu.TouchDeviceVariant.NONE.ordinal();
public Apple2KeypadChooser(Apple2Activity activity, ArrayList<Apple2MenuView> viewStack) {
mActivity = activity;
@@ -50,6 +50,36 @@ public class Apple2KeypadChooser implements Apple2MenuView {
return true;
}
public static boolean isShiftedKey(char ascii) {
switch (ascii) {
case '~':
case '!':
case '@':
case '#':
case '$':
case '%':
case '^':
case '&':
case '*':
case '(':
case ')':
case '_':
case '+':
case '{':
case '}':
case '|':
case ':':
case '"':
case '<':
case '>':
case '?':
return true;
default:
return false;
}
}
public void onKeyTapCalibrationEvent(char ascii, int scancode) {
if (ascii == Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION) {
scancode = -1;
@@ -58,37 +88,34 @@ public class Apple2KeypadChooser implements Apple2MenuView {
return;
}
String asciiStr = asciiRepresentation(ascii);
Log.d(TAG, "ascii:'" + asciiStr + "' scancode:" + scancode);
mChooserState.setValues(mActivity, ascii, scancode);
Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD.ordinal());
mCurrentChoicePrompt.setText(getNextChoiceString() + asciiStr);
switch (mChooserState) {
case CHOOSE_TAP:
mActivity.nativeOnTouch(MotionEvent.ACTION_DOWN, 1, 0, new float[]{400.f}, new float[]{400.f});
mActivity.nativeOnTouch(MotionEvent.ACTION_UP, 1, 0, new float[]{400.f}, new float[]{400.f});
break;
case CHOOSE_SWIPEDOWN:
mActivity.nativeOnTouch(MotionEvent.ACTION_DOWN, 1, 0, new float[]{400.f}, new float[]{400.f});
mActivity.nativeOnTouch(MotionEvent.ACTION_MOVE, 1, 0, new float[]{400.f}, new float[]{600.f});
mActivity.nativeOnTouch(MotionEvent.ACTION_UP, 1, 0, new float[]{400.f}, new float[]{600.f});
break;
case CHOOSE_SWIPEUP:
mActivity.nativeOnTouch(MotionEvent.ACTION_DOWN, 1, 0, new float[]{400.f}, new float[]{400.f});
mActivity.nativeOnTouch(MotionEvent.ACTION_MOVE, 1, 0, new float[]{400.f}, new float[]{200.f});
mActivity.nativeOnTouch(MotionEvent.ACTION_UP, 1, 0, new float[]{400.f}, new float[]{200.f});
break;
default:
break;
String asciiStr = asciiRepresentation(mActivity, ascii);
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "ascii:'" + asciiStr + "' scancode:" + scancode);
if (ascii == ' ') {
ascii = Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE;
}
Apple2KeypadSettingsMenu.KeyTuple tuple = new Apple2KeypadSettingsMenu.KeyTuple((long) ascii, (long) scancode, isShiftedKey(ascii));
mChooserState.setKey(mActivity, tuple);
Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD.ordinal());
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN);
mCurrentChoicePrompt.setText(getNextChoiceString() + asciiStr);
calibrationContinue();
}
private void calibrationContinue() {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
mChooserState = mChooserState.next();
mCurrentChoicePrompt.setText(getNextChoiceString());
Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.KEYBOARD.ordinal());
if (mChooserState.ordinal() == 0) {
dismiss();
} else {
mCurrentChoicePrompt.setText(getNextChoiceString());
Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, Apple2SettingsMenu.TouchDeviceVariant.KEYBOARD.ordinal());
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN);
}
}
}, 1000);
}
@@ -101,15 +128,19 @@ public class Apple2KeypadChooser implements Apple2MenuView {
}
public void dismiss() {
for (Apple2MenuView apple2MenuView : mViewStack) {
if (apple2MenuView != this) {
mActivity.pushApple2View(apple2MenuView);
}
}
Apple2Preferences.nativeTouchDeviceEndCalibrationMode();
Apple2Preferences.nativeSetTouchMenuEnabled(mTouchMenuEnabled);
Apple2Preferences.nativeSetCurrentTouchDevice(mSavedTouchDevice);
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, false);
Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, mTouchMenuEnabled);
Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, mSavedTouchDevice);
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN);
mActivity.popApple2View(this);
}
@@ -130,6 +161,8 @@ public class Apple2KeypadChooser implements Apple2MenuView {
// internals
private void setup() {
mChooserState.start();
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mSettingsView = inflater.inflate(R.layout.activity_chooser_keypad, null, false);
@@ -140,44 +173,65 @@ public class Apple2KeypadChooser implements Apple2MenuView {
skipButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Apple2KeypadChooser.this.onKeyTapCalibrationEvent((char)Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1);
Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD.ordinal());
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN);
calibrationContinue();
}
});
Button noneButton = (Button) mSettingsView.findViewById(R.id.noneButton);
noneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onKeyTapCalibrationEvent((char) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1);
}
});
// temporarily undo these native touch settings while calibrating...
mTouchMenuEnabled = Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(mActivity);
Apple2Preferences.nativeSetTouchMenuEnabled(false);
mSavedTouchDevice = Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(mActivity);
Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.KEYBOARD.ordinal());
mTouchMenuEnabled = (boolean) Apple2Preferences.getJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED);
Apple2Preferences.setJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.TOUCH_MENU_ENABLED, false);
mSavedTouchDevice = (int) Apple2Preferences.getJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT);
Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, Apple2SettingsMenu.TouchDeviceVariant.KEYBOARD.ordinal());
Apple2Preferences.nativeTouchDeviceBeginCalibrationMode();
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, true);
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN);
}
private String asciiRepresentation(char ascii) {
public static String asciiRepresentation(Apple2Activity activity, char ascii) {
switch (ascii) {
case Apple2KeyboardSettingsMenu.MOUSETEXT_OPENAPPLE:
return mActivity.getResources().getString(R.string.key_open_apple);
return activity.getResources().getString(R.string.key_open_apple);
case Apple2KeyboardSettingsMenu.MOUSETEXT_CLOSEDAPPLE:
return mActivity.getResources().getString(R.string.key_closed_apple);
return activity.getResources().getString(R.string.key_closed_apple);
case Apple2KeyboardSettingsMenu.kUP:
case Apple2KeyboardSettingsMenu.MOUSETEXT_UP:
return mActivity.getResources().getString(R.string.key_up);
return activity.getResources().getString(R.string.key_up);
case Apple2KeyboardSettingsMenu.kLT:
case Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT:
return mActivity.getResources().getString(R.string.key_left);
return activity.getResources().getString(R.string.key_left);
case Apple2KeyboardSettingsMenu.kRT:
case Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT:
return mActivity.getResources().getString(R.string.key_right);
return activity.getResources().getString(R.string.key_right);
case Apple2KeyboardSettingsMenu.kDN:
case Apple2KeyboardSettingsMenu.MOUSETEXT_DOWN:
return mActivity.getResources().getString(R.string.key_down);
return activity.getResources().getString(R.string.key_down);
case Apple2KeyboardSettingsMenu.ICONTEXT_CTRL:
return mActivity.getResources().getString(R.string.key_ctrl);
return activity.getResources().getString(R.string.key_ctrl);
case Apple2KeyboardSettingsMenu.kESC:
case Apple2KeyboardSettingsMenu.ICONTEXT_ESC:
return mActivity.getResources().getString(R.string.key_esc);
return activity.getResources().getString(R.string.key_esc);
case Apple2KeyboardSettingsMenu.kRET:
case Apple2KeyboardSettingsMenu.ICONTEXT_RETURN:
return mActivity.getResources().getString(R.string.key_ret);
return activity.getResources().getString(R.string.key_ret);
case Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION:
return mActivity.getResources().getString(R.string.key_none);
return activity.getResources().getString(R.string.key_none);
case ' ':
case Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE:
return mActivity.getResources().getString(R.string.key_space);
return activity.getResources().getString(R.string.key_space);
case Apple2KeyboardSettingsMenu.kDEL:
return activity.getResources().getString(R.string.key_del);
case Apple2KeyboardSettingsMenu.kTAB:
return activity.getResources().getString(R.string.key_tab);
default:
return "" + ascii;
}
@@ -189,145 +243,179 @@ public class Apple2KeypadChooser implements Apple2MenuView {
}
private enum STATE_MACHINE {
CHOOSE_NORTHWEST {
CHOOSE_AXIS_NORTHWEST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_ul);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_NORTHWEST_KEY.saveChosenKey(activity, ascii, scancode);
}
},
CHOOSE_NORTH {
CHOOSE_AXIS_NORTH {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_up);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_NORTH_KEY.saveChosenKey(activity, ascii, scancode);
}
},
CHOOSE_NORTHEAST {
CHOOSE_AXIS_NORTHEAST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_ur);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_NORTHEAST_KEY.saveChosenKey(activity, ascii, scancode);
}
},
CHOOSE_WEST {
CHOOSE_AXIS_WEST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_l);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_WEST_KEY.saveChosenKey(activity, ascii, scancode);
}
},
CHOOSE_CENTER {
CHOOSE_AXIS_CENTER {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_c);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_CENTER_KEY.saveChosenKey(activity, ascii, scancode);
}
},
CHOOSE_EAST {
CHOOSE_AXIS_EAST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_r);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_EAST_KEY.saveChosenKey(activity, ascii, scancode);
}
},
CHOOSE_SOUTHWEST {
CHOOSE_AXIS_SOUTHWEST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_dl);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_SOUTHWEST_KEY.saveChosenKey(activity, ascii, scancode);
}
},
CHOOSE_SOUTH {
CHOOSE_AXIS_SOUTH {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_dn);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_SOUTH_KEY.saveChosenKey(activity, ascii, scancode);
}
},
CHOOSE_SOUTHEAST {
CHOOSE_AXIS_SOUTHEAST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_dr);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_SOUTHEAST_KEY.saveChosenKey(activity, ascii, scancode);
}
},
CHOOSE_TAP {
CHOOSE_BUTT_NORTHWEST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_button_tap);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_TAP_KEY.saveChosenKey(activity, ascii, scancode);
return activity.getResources().getString(R.string.keypad_key_axis_ul);
}
},
CHOOSE_SWIPEUP {
CHOOSE_BUTT_NORTH {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_button_swipeup);
}
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_SWIPEUP_KEY.saveChosenKey(activity, ascii, scancode);
return activity.getResources().getString(R.string.keypad_key_axis_up);
}
},
CHOOSE_SWIPEDOWN {
CHOOSE_BUTT_NORTHEAST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_button_swipedown);
return activity.getResources().getString(R.string.keypad_key_axis_ur);
}
},
CHOOSE_BUTT_WEST {
@Override
public void setValues(Apple2Activity activity, char ascii, int scancode) {
Apple2Preferences.KEYPAD_SWIPEDOWN_KEY.saveChosenKey(activity, ascii, scancode);
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_l);
}
},
CHOOSE_BUTT_CENTER {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_c);
}
},
CHOOSE_BUTT_EAST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_r);
}
},
CHOOSE_BUTT_SOUTHWEST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_dl);
}
},
CHOOSE_BUTT_SOUTH {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_dn);
}
},
CHOOSE_BUTT_SOUTHEAST {
@Override
public String getKeyName(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_key_axis_dr);
}
};
public static final int size = STATE_MACHINE.values().length;
public abstract void setValues(Apple2Activity activity, char ascii, int scancode);
private static ArrayList<Apple2KeypadSettingsMenu.KeyTuple> axisRosette = new ArrayList<Apple2KeypadSettingsMenu.KeyTuple>();
private static ArrayList<Apple2KeypadSettingsMenu.KeyTuple> buttRosette = new ArrayList<Apple2KeypadSettingsMenu.KeyTuple>();
public void setKey(Apple2Activity activity, Apple2KeypadSettingsMenu.KeyTuple tuple) {
int ord = ordinal();
int buttbegin = CHOOSE_BUTT_NORTHWEST.ordinal();
if (ord < buttbegin) {
axisRosette.set(ord, tuple);
Apple2KeypadSettingsMenu.KeypadPreset.saveAxisRosette(axisRosette);
} else {
ord -= buttbegin;
buttRosette.set(ord, tuple);
Apple2KeypadSettingsMenu.KeypadPreset.saveButtRosette(buttRosette);
}
Apple2Preferences.sync(activity, Apple2Preferences.PREF_DOMAIN_JOYSTICK);
}
public abstract String getKeyName(Apple2Activity activity);
public void start() {
setupCharsAndScans(axisRosette, Apple2KeypadSettingsMenu.PREF_KPAD_AXIS_ROSETTE);
setupCharsAndScans(buttRosette, Apple2KeypadSettingsMenu.PREF_KPAD_BUTT_ROSETTE);
}
private void setupCharsAndScans(final ArrayList<Apple2KeypadSettingsMenu.KeyTuple> rosette, final String pref) {
rosette.clear();
try {
JSONArray jsonArray = (JSONArray) Apple2Preferences.getJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, pref, null);
if (jsonArray == null) {
jsonArray = new JSONArray();
for (int i = 0; i < Apple2KeypadSettingsMenu.ROSETTE_SIZE; i++) {
JSONObject map = new JSONObject();
map.put("ch", (long) Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION);
map.put("scan", -1L);
map.put("isShifted", false);
}
}
int len = jsonArray.length();
if (len != Apple2KeypadSettingsMenu.ROSETTE_SIZE) {
throw new RuntimeException("rosette not expected length");
}
for (int i = 0; i < len; i++) {
JSONObject obj = jsonArray.getJSONObject(i);
long ch = obj.getLong("ch");
long scan = obj.getLong("scan");
boolean isShifted = obj.getBoolean("isShifted");
rosette.add(new Apple2KeypadSettingsMenu.KeyTuple(ch, scan, isShifted));
}
} catch (JSONException e) {
e.printStackTrace();
}
if (rosette.size() != Apple2KeypadSettingsMenu.ROSETTE_SIZE) {
throw new RuntimeException("rosette chars is not correct size");
}
}
public STATE_MACHINE next() {
int nextOrd = this.ordinal() + 1;
if (nextOrd >= size) {

View File

@@ -12,18 +12,35 @@
package org.deadc0de.apple2ix;
import android.view.View;
import android.widget.TextView;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.Toast;
import java.util.ArrayList;
import org.deadc0de.apple2ix.basic.R;
import org.json.JSONArray;
import org.json.JSONObject;
public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu {
private final static String TAG = "Apple2KeypadSettingsMenu";
public Apple2KeypadSettingsMenu(Apple2Activity activity) {
public final static String PREF_KPAD_AXIS_ROSETTE = "kpAxisRosette";
public final static String PREF_KPAD_BUTT_ROSETTE = "kpButtRosette";
public final static int ROSETTE_SIZE = 9;
private Apple2SettingsMenu.TouchDeviceVariant mVariant;
public Apple2KeypadSettingsMenu(Apple2Activity activity, Apple2SettingsMenu.TouchDeviceVariant variant) {
super(activity);
if (!(variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK || variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD)) {
throw new RuntimeException("You're doing it wrong");
}
mVariant = variant;
}
@Override
@@ -49,7 +66,477 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu {
return true;
}
public static class KeyTuple {
public long ch;
public long scan;
public boolean isShifted;
public KeyTuple(long ch, long scan) {
this(ch, scan, false);
}
public KeyTuple(long ch, long scan, boolean isShifted) {
this.ch = ch;
this.scan = scan;
this.isShifted = isShifted;
}
}
public enum KeypadPreset {
ARROWS_SPACE {
@Override
public String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_arrows_space);
}
@Override
public void apply(Apple2Activity activity) {
{
ArrayList<KeyTuple> axisRosette = new ArrayList<KeyTuple>();
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_UP, Apple2KeyboardSettingsMenu.SCANCODE_UP));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_DOWN, Apple2KeyboardSettingsMenu.SCANCODE_DOWN));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
saveAxisRosette(axisRosette);
}
{
ArrayList<KeyTuple> buttRosette = new ArrayList<KeyTuple>();
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
saveButtRosette(buttRosette);
}
}
},
AZ_LEFT_RIGHT_SPACE {
@Override
public String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_az_left_right_space);
}
@Override
public void apply(Apple2Activity activity) {
{
ArrayList<KeyTuple> axisRosette = new ArrayList<KeyTuple>();
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('A', Apple2KeyboardSettingsMenu.SCANCODE_A));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('Z', Apple2KeyboardSettingsMenu.SCANCODE_Z));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
saveAxisRosette(axisRosette);
}
{
ArrayList<KeyTuple> buttRosette = new ArrayList<KeyTuple>();
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
saveButtRosette(buttRosette);
}
}
},
LEFT_RIGHT_SPACE {
@Override
public String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_left_right_space);
}
@Override
public void apply(Apple2Activity activity) {
{
ArrayList<KeyTuple> axisRosette = new ArrayList<KeyTuple>();
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_LEFT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_LEFT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT));
saveAxisRosette(axisRosette);
}
{
ArrayList<KeyTuple> buttRosette = new ArrayList<KeyTuple>();
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
saveButtRosette(buttRosette);
}
}
},
QAZ_LEFT_RIGHT_SPACE {
@Override
public String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_qaz_left_right_space);
}
@Override
public void apply(Apple2Activity activity) {
{
ArrayList<KeyTuple> axisRosette = new ArrayList<KeyTuple>();
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('A', Apple2KeyboardSettingsMenu.SCANCODE_A));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('Z', Apple2KeyboardSettingsMenu.SCANCODE_Z));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
saveAxisRosette(axisRosette);
}
{
ArrayList<KeyTuple> buttRosette = new ArrayList<KeyTuple>();
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_Q));
buttRosette.add(new KeyTuple('Q', Apple2KeyboardSettingsMenu.SCANCODE_Q));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_Q));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
saveButtRosette(buttRosette);
}
}
},
IJKM_SPACE {
@Override
public String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_ijkm_space);
}
@Override
public void apply(Apple2Activity activity) {
{
ArrayList<KeyTuple> axisRosette = new ArrayList<KeyTuple>();
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('I', Apple2KeyboardSettingsMenu.SCANCODE_I));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('J', Apple2KeyboardSettingsMenu.SCANCODE_J));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('K', Apple2KeyboardSettingsMenu.SCANCODE_K));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('M', Apple2KeyboardSettingsMenu.SCANCODE_M));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
saveAxisRosette(axisRosette);
}
{
ArrayList<KeyTuple> buttRosette = new ArrayList<KeyTuple>();
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
saveButtRosette(buttRosette);
}
}
},
WADX_SPACE {
@Override
public String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_wadx_space);
}
@Override
public void apply(Apple2Activity activity) {
{
ArrayList<KeyTuple> axisRosette = new ArrayList<KeyTuple>();
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('W', Apple2KeyboardSettingsMenu.SCANCODE_W));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('A', Apple2KeyboardSettingsMenu.SCANCODE_A));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('D', Apple2KeyboardSettingsMenu.SCANCODE_D));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('X', Apple2KeyboardSettingsMenu.SCANCODE_X));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
saveAxisRosette(axisRosette);
}
{
ArrayList<KeyTuple> buttRosette = new ArrayList<KeyTuple>();
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
saveButtRosette(buttRosette);
}
}
},
LODERUNNER_KEYS {
@Override
public String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_loderunner);
}
@Override
public String getToast(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_loderunner_toast);
}
@Override
public void apply(Apple2Activity activity) {
{
ArrayList<KeyTuple> axisRosette = new ArrayList<KeyTuple>();
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('I', Apple2KeyboardSettingsMenu.SCANCODE_I));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('J', Apple2KeyboardSettingsMenu.SCANCODE_J));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
axisRosette.add(new KeyTuple('L', Apple2KeyboardSettingsMenu.SCANCODE_L));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
axisRosette.add(new KeyTuple('K', Apple2KeyboardSettingsMenu.SCANCODE_K));
axisRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
saveAxisRosette(axisRosette);
}
{
ArrayList<KeyTuple> buttRosette = new ArrayList<KeyTuple>();
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_U));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_O));
buttRosette.add(new KeyTuple('U', Apple2KeyboardSettingsMenu.SCANCODE_U));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
buttRosette.add(new KeyTuple('O', Apple2KeyboardSettingsMenu.SCANCODE_O));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_U));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_O));
saveButtRosette(buttRosette);
}
}
},
ROBOTRON_KEYS {
@Override
public String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_robotron);
}
@Override
public void apply(Apple2Activity activity) {
{
ArrayList<KeyTuple> axisRosette = new ArrayList<KeyTuple>();
axisRosette.add(new KeyTuple('Q', Apple2KeyboardSettingsMenu.SCANCODE_Q));
axisRosette.add(new KeyTuple('W', Apple2KeyboardSettingsMenu.SCANCODE_W));
axisRosette.add(new KeyTuple('E', Apple2KeyboardSettingsMenu.SCANCODE_E));
axisRosette.add(new KeyTuple('A', Apple2KeyboardSettingsMenu.SCANCODE_A));
axisRosette.add(new KeyTuple('S', Apple2KeyboardSettingsMenu.SCANCODE_S));
axisRosette.add(new KeyTuple('D', Apple2KeyboardSettingsMenu.SCANCODE_D));
axisRosette.add(new KeyTuple('Z', Apple2KeyboardSettingsMenu.SCANCODE_Z));
axisRosette.add(new KeyTuple('X', Apple2KeyboardSettingsMenu.SCANCODE_X));
axisRosette.add(new KeyTuple('C', Apple2KeyboardSettingsMenu.SCANCODE_C));
saveAxisRosette(axisRosette);
}
{
ArrayList<KeyTuple> buttRosette = new ArrayList<KeyTuple>();
buttRosette.add(new KeyTuple('I', Apple2KeyboardSettingsMenu.SCANCODE_I));
buttRosette.add(new KeyTuple('O', Apple2KeyboardSettingsMenu.SCANCODE_O));
buttRosette.add(new KeyTuple('P', Apple2KeyboardSettingsMenu.SCANCODE_P));
buttRosette.add(new KeyTuple('K', Apple2KeyboardSettingsMenu.SCANCODE_K));
buttRosette.add(new KeyTuple('L', Apple2KeyboardSettingsMenu.SCANCODE_L));
buttRosette.add(new KeyTuple(';', Apple2KeyboardSettingsMenu.SCANCODE_SEMICOLON));
buttRosette.add(new KeyTuple(',', Apple2KeyboardSettingsMenu.SCANCODE_COMMA));
buttRosette.add(new KeyTuple('.', Apple2KeyboardSettingsMenu.SCANCODE_PERIOD));
buttRosette.add(new KeyTuple('/', Apple2KeyboardSettingsMenu.SCANCODE_SLASH));
saveButtRosette(buttRosette);
}
}
},
SEAFOX_KEYS {
@Override
public String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_preset_seafox);
}
@Override
public void apply(Apple2Activity activity) {
{
ArrayList<KeyTuple> axisRosette = new ArrayList<KeyTuple>();
axisRosette.add(new KeyTuple('Y', Apple2KeyboardSettingsMenu.SCANCODE_Y));
axisRosette.add(new KeyTuple('U', Apple2KeyboardSettingsMenu.SCANCODE_U));
axisRosette.add(new KeyTuple('I', Apple2KeyboardSettingsMenu.SCANCODE_I));
axisRosette.add(new KeyTuple('H', Apple2KeyboardSettingsMenu.SCANCODE_H));
axisRosette.add(new KeyTuple('J', Apple2KeyboardSettingsMenu.SCANCODE_J));
axisRosette.add(new KeyTuple('K', Apple2KeyboardSettingsMenu.SCANCODE_K));
axisRosette.add(new KeyTuple('N', Apple2KeyboardSettingsMenu.SCANCODE_N));
axisRosette.add(new KeyTuple('M', Apple2KeyboardSettingsMenu.SCANCODE_M));
axisRosette.add(new KeyTuple(',', Apple2KeyboardSettingsMenu.SCANCODE_COMMA));
saveAxisRosette(axisRosette);
}
{
ArrayList<KeyTuple> buttRosette = new ArrayList<KeyTuple>();
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_D));
buttRosette.add(new KeyTuple('D', Apple2KeyboardSettingsMenu.SCANCODE_D));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_F));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE));
buttRosette.add(new KeyTuple('F', Apple2KeyboardSettingsMenu.SCANCODE_F));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
buttRosette.add(new KeyTuple(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1));
saveButtRosette(buttRosette);
}
}
};
public static void saveAxisRosette(ArrayList<KeyTuple> axisRosette) {
if (axisRosette.size() != 9) {
throw new RuntimeException("axis rosette is not correct size");
}
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_AXIS_ROSETTE, toJSONArray(axisRosette));
}
public static void saveButtRosette(ArrayList<KeyTuple> buttRosette) {
if (buttRosette.size() != 9) {
throw new RuntimeException("butt rosette is not correct size");
}
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_BUTT_ROSETTE, toJSONArray(buttRosette));
}
private static JSONArray toJSONArray(ArrayList<KeyTuple> rosette) {
JSONArray jsonArray = new JSONArray();
try {
for (KeyTuple tuple : rosette) {
JSONObject obj = new JSONObject();
obj.put("ch", tuple.ch);
obj.put("scan", tuple.scan);
obj.put("isShifted", tuple.isShifted);
jsonArray.put(obj);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return jsonArray;
}
public abstract String getTitle(Apple2Activity activity);
public abstract void apply(Apple2Activity activity);
public String getToast(Apple2Activity activity) {
return null;
}
public static final int size = KeypadPreset.values().length;
public static String[] titles(Apple2Activity activity) {
String[] titles = new String[size];
int i = 0;
for (KeypadPreset preset : values()) {
titles[i++] = preset.getTitle(activity);
}
return titles;
}
}
enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
JOYSTICK_CALIBRATE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_calibrate);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.joystick_calibrate_summary);
}
@Override
public String getPrefKey() {
return null;
}
@Override
public Object getPrefDefault() {
return null;
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
Apple2KeypadSettingsMenu thisMenu = (Apple2KeypadSettingsMenu)settingsMenu;
Apple2JoystickCalibration.startCalibration(activity, thisMenu.mVariant);
}
},
KEYPAD_CHOOSE_KEYS {
@Override
public final String getTitle(Apple2Activity activity) {
@@ -61,6 +548,16 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.keypad_choose_summary);
}
@Override
public String getPrefKey() {
return "kpPresetChoice";
}
@Override
public Object getPrefDefault() {
return KeypadPreset.IJKM_SPACE.ordinal() + 1;
}
@Override
public final View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
@@ -70,70 +567,74 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
String[] titles = new String[Apple2Preferences.KeypadPreset.size + 1];
final IMenuEnum self = this;
String[] titles = new String[KeypadPreset.size + 1];
titles[0] = activity.getResources().getString(R.string.keypad_preset_custom);
System.arraycopy(Apple2Preferences.KeypadPreset.titles(activity), 0, titles, 1, Apple2Preferences.KeypadPreset.size);
System.arraycopy(KeypadPreset.titles(activity), 0, titles, 1, KeypadPreset.size);
_alertDialogHandleSelection(activity, R.string.keypad_choose_title, titles, new IPreferenceLoadSave() {
@Override
public int intValue() {
return Apple2Preferences.KEYPAD_KEYS.intValue(activity);
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
public void saveInt(int value) {
Apple2Preferences.KEYPAD_KEYS.saveInt(activity, value);
Apple2Preferences.setJSONPref(self, value);
if (value == 0) {
Apple2KeypadSettingsMenu keypadSettingsMenu = (Apple2KeypadSettingsMenu) settingsMenu;
keypadSettingsMenu.chooseKeys(activity);
} else {
Apple2Preferences.KeypadPreset.values()[value - 1].apply(activity);
KeypadPreset.values()[value - 1].apply(activity);
String toast = KeypadPreset.values()[value - 1].getToast(activity);
if (toast != null) {
Toast.makeText(activity, toast, Toast.LENGTH_SHORT).show();
}
}
}
});
}
},
KEYPAD_CALIBRATE {
FAST_AUTOREPEAT {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_calibrate);
return activity.getResources().getString(R.string.keypad_autorepeat_fast);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_calibrate_summary);
return activity.getResources().getString(R.string.keypad_autorepeat_fast_summary);
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
ArrayList<Apple2MenuView> viewStack = new ArrayList<Apple2MenuView>();
{
int idx = 0;
while (true) {
Apple2MenuView apple2MenuView = activity.peekApple2View(idx);
if (apple2MenuView == null) {
break;
}
viewStack.add(apple2MenuView);
++idx;
public String getPrefKey() {
return "kpFastAutoRepeat";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref(self, isChecked);
}
}
Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD);
// show this new view...
calibration.show();
// ...with nothing else underneath 'cept the emulator OpenGL layer
for (Apple2MenuView apple2MenuView : viewStack) {
activity.popApple2View(apple2MenuView);
}
});
return convertView;
}
},
KEYPAD_ADVANCED {
JOYSTICK_ADVANCED {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.settings_advanced);
return activity.getResources().getString(R.string.settings_advanced_joystick);
}
@Override
@@ -143,15 +644,29 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
new Apple2KeypadSettingsMenu.KeypadAdvanced(activity).show();
new Apple2JoystickSettingsMenu.JoystickAdvanced(activity).show();
}
};
public static final int size = SETTINGS.values().length;
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_JOYSTICK;
}
@Override
public String getPrefKey() {
return null;
}
@Override
public Object getPrefDefault() {
return null;
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
/* ... */
}
@Override
@@ -196,108 +711,4 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu {
activity.popApple2View(apple2MenuView);
}
}
protected static class KeypadAdvanced extends Apple2AbstractMenu {
private final static String TAG = "KeypadAdvanced";
public KeypadAdvanced(Apple2Activity activity) {
super(activity);
}
@Override
public final String[] allTitles() {
return SETTINGS.titles(mActivity);
}
@Override
public final IMenuEnum[] allValues() {
return SETTINGS.values();
}
@Override
public final boolean areAllItemsEnabled() {
return false;
}
@Override
public final boolean isEnabled(int position) {
if (position < 0 || position >= SETTINGS.size) {
throw new ArrayIndexOutOfBoundsException();
}
return position == SETTINGS.JOYSTICK_ADVANCED.ordinal();
}
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
KEYREPEAT_THRESHOLD {
@Override
public final String getTitle(Apple2Activity activity) {
return "";
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.keypad_repeat_summary);
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.KEYREPEAT_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.KEYREPEAT_THRESHOLD.saveInt(activity, progress);
}
@Override
public int intValue() {
return Apple2Preferences.KEYREPEAT_THRESHOLD.intValue(activity);
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
seekBarValue.setText("" + ((float) progress / Apple2Preferences.KEYREPEAT_NUM_CHOICES));
}
});
}
},
JOYSTICK_ADVANCED {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.settings_advanced_joystick);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.settings_advanced_joystick_summary);
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
new Apple2JoystickSettingsMenu.JoystickAdvanced(activity).show();
}
};
public static final int size = SETTINGS.values().length;
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
/* ... */
}
@Override
public View getView(Apple2Activity activity, View convertView) {
return _basicView(activity, this, convertView);
}
public static String[] titles(Apple2Activity activity) {
String[] titles = new String[size];
int i = 0;
for (SETTINGS setting : values()) {
titles[i++] = setting.getTitle(activity);
}
return titles;
}
}
}
}

View File

@@ -12,8 +12,13 @@
package org.deadc0de.apple2ix;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -24,19 +29,31 @@ import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import org.deadc0de.apple2ix.basic.R;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
public class Apple2MainMenu {
private final static int MENU_INSET = 20;
public final static String OLD_SAVE_FILE = "emulator.state";
public final static String SAVE_FILE_EXTENSION = ".a2state";
public final static String SAVE_FILE = "emulator" + SAVE_FILE_EXTENSION;
private final static String TAG = "Apple2MainMenu";
private Apple2Activity mActivity = null;
private Apple2View mParentView = null;
private PopupWindow mMainMenuPopup = null;
private AtomicBoolean mShowingRebootQuit = new AtomicBoolean(false);
private AtomicBoolean mShowingSaveRestore = new AtomicBoolean(false);
public Apple2MainMenu(Apple2Activity activity, Apple2View parent) {
mActivity = activity;
mParentView = parent;
@@ -45,52 +62,82 @@ public class Apple2MainMenu {
enum SETTINGS {
SHOW_SETTINGS {
@Override public String getTitle(Context ctx) {
@Override
public String getTitle(Context ctx) {
return ctx.getResources().getString(R.string.menu_settings);
}
@Override public String getSummary(Context ctx) {
@Override
public String getSummary(Context ctx) {
return ctx.getResources().getString(R.string.menu_settings_summary);
}
@Override public void handleSelection(Apple2MainMenu mainMenu) {
@Override
public void handleSelection(Apple2MainMenu mainMenu) {
mainMenu.showSettings();
}
},
LOAD_DISK {
@Override public String getTitle(Context ctx) {
@Override
public String getTitle(Context ctx) {
return ctx.getResources().getString(R.string.menu_disks);
}
@Override public String getSummary(Context ctx) {
@Override
public String getSummary(Context ctx) {
return ctx.getResources().getString(R.string.menu_disks_summary);
}
@Override public void handleSelection(Apple2MainMenu mainMenu) {
@Override
public void handleSelection(Apple2MainMenu mainMenu) {
mainMenu.showDisksMenu();
}
},
SAVE_RESTORE {
@Override public String getTitle(Context ctx) {
@Override
public String getTitle(Context ctx) {
return ctx.getResources().getString(R.string.saverestore);
}
@Override public String getSummary(Context ctx) {
@Override
public String getSummary(Context ctx) {
return ctx.getResources().getString(R.string.saverestore_summary);
}
@Override public void handleSelection(Apple2MainMenu mainMenu) {
mainMenu.mActivity.maybeSaveRestore();
@Override
public void handleSelection(Apple2MainMenu mainMenu) {
if (!mainMenu.mShowingSaveRestore.compareAndSet(false, true)) {
Log.v(TAG, "OMG, avoiding nasty UI race around sync/restore");
return;
}
mainMenu.maybeSaveRestore();
}
},
REBOOT_QUIT_EMULATOR {
@Override public String getTitle(Context ctx) {
@Override
public String getTitle(Context ctx) {
return ctx.getResources().getString(R.string.quit_reboot);
}
@Override public String getSummary(Context ctx) {
@Override
public String getSummary(Context ctx) {
return "";
}
@Override public void handleSelection(Apple2MainMenu mainMenu) {
mainMenu.mActivity.maybeRebootQuit();
@Override
public void handleSelection(Apple2MainMenu mainMenu) {
if (!mainMenu.mShowingRebootQuit.compareAndSet(false, true)) {
Log.v(TAG, "OMG, avoiding nasty UI race around quit/reboot");
return;
}
mainMenu.maybeRebootQuit();
}
};
public abstract String getTitle(Context ctx);
public abstract String getSummary(Context ctx);
public abstract void handleSelection(Apple2MainMenu mainMenu);
public static String[] titles(Context ctx) {
@@ -105,11 +152,11 @@ public class Apple2MainMenu {
private void setup() {
LayoutInflater inflater = (LayoutInflater)mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View listLayout=inflater.inflate(R.layout.activity_main_menu, null, false);
ListView mainMenuView = (ListView)listLayout.findViewById(R.id.main_popup_menu);
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View listLayout = inflater.inflate(R.layout.activity_main_menu, null, false);
ListView mainMenuView = (ListView) listLayout.findViewById(R.id.main_popup_menu);
mainMenuView.setEnabled(true);
LinearLayout mainPopupContainer = (LinearLayout)listLayout.findViewById(R.id.main_popup_container);
LinearLayout mainPopupContainer = (LinearLayout) listLayout.findViewById(R.id.main_popup_container);
final String[] values = SETTINGS.titles(mActivity);
@@ -118,10 +165,11 @@ public class Apple2MainMenu {
public boolean areAllItemsEnabled() {
return true;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
TextView tv = (TextView)view.findViewById(android.R.id.text2);
TextView tv = (TextView) view.findViewById(android.R.id.text2);
SETTINGS setting = SETTINGS.values()[position];
tv.setText(setting.getSummary(mActivity));
return view;
@@ -131,14 +179,14 @@ public class Apple2MainMenu {
mainMenuView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d(TAG, "position:"+position+" tapped...");
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "position:" + position + " tapped...");
SETTINGS setting = SETTINGS.values()[position];
setting.handleSelection(Apple2MainMenu.this);
}
});
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) {
mMainMenuPopup = new PopupWindow(mainPopupContainer, android.app.ActionBar.LayoutParams.WRAP_CONTENT, android.app.ActionBar.LayoutParams.WRAP_CONTENT, true);
mMainMenuPopup = new PopupWindow(mainPopupContainer, ActionBar.LayoutParams.WRAP_CONTENT, ActionBar.LayoutParams.WRAP_CONTENT, true);
} else {
// 2015/03/11 ... there may well be a less hackish way to support Gingerbread, but eh ... diminishing returns
final int TOTAL_MARGINS = 16;
@@ -153,7 +201,7 @@ public class Apple2MainMenu {
maxWidth = width;
}
}
mMainMenuPopup = new PopupWindow(mainPopupContainer, maxWidth+TOTAL_MARGINS, totalHeight, true);
mMainMenuPopup = new PopupWindow(mainPopupContainer, maxWidth + TOTAL_MARGINS, totalHeight, true);
}
// This kludgery allows touching the outside or back-buttoning to dismiss
@@ -162,8 +210,8 @@ public class Apple2MainMenu {
mMainMenuPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
Apple2MainMenu.this.mActivity.maybeResumeCPU();
}
Apple2MainMenu.this.mActivity.maybeResumeEmulation();
}
});
}
@@ -184,7 +232,10 @@ public class Apple2MainMenu {
return;
}
mActivity.nativeEmulationPause();
mShowingRebootQuit.set(false);
mShowingSaveRestore.set(false);
mActivity.pauseEmulation();
mMainMenuPopup.showAtLocation(mParentView, Gravity.CENTER, 0, 0);
}
@@ -199,4 +250,221 @@ public class Apple2MainMenu {
public boolean isShowing() {
return mMainMenuPopup.isShowing();
}
public void maybeRebootQuit() {
mActivity.pauseEmulation();
final AtomicBoolean selectionAlreadyHandled = new AtomicBoolean(false);
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View resetConfirmationView = inflater.inflate(R.layout.a2reset_confirmation, null, false);
final RadioButton openAppleSelected = (RadioButton) resetConfirmationView.findViewById(R.id.radioButton_openApple);
openAppleSelected.setChecked(true);
final RadioButton closedAppleSelected = (RadioButton) resetConfirmationView.findViewById(R.id.radioButton_closedApple);
closedAppleSelected.setChecked(false);
final RadioButton noAppleSelected = (RadioButton) resetConfirmationView.findViewById(R.id.radioButton_noApple);
noAppleSelected.setChecked(false);
AlertDialog rebootQuitDialog = new AlertDialog.Builder(mActivity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.quit_reboot).setMessage(R.string.quit_reboot_choice).setView(resetConfirmationView).setNeutralButton(R.string.reset, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!selectionAlreadyHandled.compareAndSet(false, true)) {
Log.v(TAG, "OMG, avoiding nasty UI race in reboot/quit onClick()");
return;
}
int resetState = 0;
if (openAppleSelected.isChecked()) {
resetState = 1;
} else if (closedAppleSelected.isChecked()) {
resetState = 2;
}
mActivity.rebootEmulation(resetState);
Apple2MainMenu.this.dismiss();
}
}).setPositiveButton(R.string.quit, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!selectionAlreadyHandled.compareAndSet(false, true)) {
Log.v(TAG, "OMG, avoiding nasty UI race in reboot/quit onClick()");
return;
}
mActivity.quitEmulator();
}
}).create();
mActivity.registerAndShowDialog(rebootQuitDialog);
}
public static boolean restoreEmulatorState(Apple2Activity activity, DiskArgs diskArgs) {
boolean restored = false;
try {
String stateFile = diskArgs.path;
if (stateFile == null) {
stateFile = Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL + diskArgs.uri.toString();
}
JSONObject map = new JSONObject();
map.put("stateFile", stateFile);
if (stateFile.startsWith(Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL)) {
if (!Apple2Utils.isExternalStorageAccessible(activity)) {
// disallow access if we cannot access external storage
throw new Exception("External storage not accessible for state load");
}
if (diskArgs.pfd == null) {
if (diskArgs.uri == null) {
String uriString = stateFile.substring(Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL.length());
diskArgs.uri = Uri.parse(uriString);
}
diskArgs.pfd = Apple2DiskChooserActivity.openFileDescriptorFromUri(activity, diskArgs.uri);
}
int fd = diskArgs.pfd.getFd(); // NPE thrown if diskArgs.pfd is null
map.put("fdState", fd);
} else {
File file = new File(stateFile);
if (!file.exists()) {
throw new RuntimeException("cannot insert state file : " + stateFile);
}
}
restored = restoreEmulatorState(activity, map.toString());
try {
diskArgs.pfd.close(); // at this point diskArgs.pfd !null
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Error attempting to close PFD : " + ioe);
}
diskArgs.pfd = null;
} catch (Exception e) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS: " + e);
}
return restored;
}
public static boolean restoreEmulatorState(Apple2Activity activity, String jsonString) {
boolean restored = false;
Apple2DisksMenu.ejectDisk(/*isDriveA:*/true);
Apple2DisksMenu.ejectDisk(/*isDriveA:*/false);
// First we extract and open the emulator.a2state disk paths (which could be in a restricted location)
jsonString = activity.stateExtractDiskPaths(jsonString);
try {
JSONObject map = new JSONObject(jsonString);
final String[] diskPathKeys = new String[]{"diskA", "diskB"};
final String[] readOnlyKeys = new String[]{"readOnlyA", "readOnlyB"};
final String[] fdKeys = new String[]{"fdA", "fdB"};
ParcelFileDescriptor[] pfds = {null, null};
for (int i = 0; i < 2; i++) {
String diskPath = map.getString(diskPathKeys[i]);
boolean readOnly = map.getBoolean(readOnlyKeys[i]);
Apple2Preferences.setJSONPref(i == 0 ? Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A : Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B, diskPath);
Apple2Preferences.setJSONPref(i == 0 ? Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_A_RO : Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B_RO, readOnly);
if (diskPath.equals("")) {
continue;
}
if (diskPath.startsWith(Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL)) {
String uriString = diskPath.substring(Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL.length());
Uri uri = Uri.parse(uriString);
pfds[i] = Apple2DiskChooserActivity.openFileDescriptorFromUri(activity, uri);
if (pfds[i] == null) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Did not find URI for drive #" + i + " specified in " + SAVE_FILE + " file : " + diskPath);
} else {
int fd = pfds[i].getFd();
map.put(fdKeys[i], fd);
}
} else {
boolean exists = new File(diskPath).exists();
if (!exists) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Did not find path for drive #" + i + " specified in " + SAVE_FILE + " file : " + diskPath);
}
}
}
jsonString = activity.loadState(map.toString());
for (int i = 0; i < 2; i++) {
try {
if (pfds[i] != null) {
pfds[i].close();
}
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Error attempting to close PFD #" + i + " : " + ioe);
}
}
map = new JSONObject(jsonString);
restored = map.getBoolean("loadStateSuccess");
} catch (Throwable t) {
Log.v(TAG, "OOPS : " + t);
}
return restored;
}
private void maybeSaveRestore() {
mActivity.pauseEmulation();
final String quickSavePath;
final File extStorage = Apple2Utils.getExternalStorageDirectory(mActivity);
if (extStorage != null) {
quickSavePath = extStorage + File.separator + SAVE_FILE;
} else {
quickSavePath = Apple2Utils.getDataDir(mActivity) + File.separator + SAVE_FILE;
}
final AtomicBoolean selectionAlreadyHandled = new AtomicBoolean(false);
AlertDialog saveRestoreDialog = new AlertDialog.Builder(mActivity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.saverestore).setMessage(R.string.saverestore_choice).setNeutralButton(R.string.save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!selectionAlreadyHandled.compareAndSet(false, true)) {
Log.v(TAG, "OMG, avoiding nasty UI race in sync/restore onClick()");
return;
}
String jsonString = "{ \"stateFile\" : \"" + quickSavePath + "\" }";
mActivity.saveState(jsonString);
Apple2MainMenu.this.dismiss();
}
}).setPositiveButton(R.string.restore, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!selectionAlreadyHandled.compareAndSet(false, true)) {
Log.v(TAG, "OMG, avoiding nasty UI race in sync/restore onClick()");
return;
}
final String jsonString = "{ \"stateFile\" : \"" + quickSavePath + "\" }";
boolean restored = restoreEmulatorState(mActivity, jsonString);
if (!restored) {
Toast.makeText(mActivity, R.string.state_not_restored, Toast.LENGTH_SHORT).show();
}
Apple2MainMenu.this.dismiss();
}
}).create();
mActivity.registerAndShowDialog(saveRestoreDialog);
}
}

View File

@@ -0,0 +1,221 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2015 Aaron Culliney
*
*/
package org.deadc0de.apple2ix;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.VerticalSeekBar;
import org.deadc0de.apple2ix.basic.R;
import java.util.ArrayList;
public class Apple2PortraitCalibration implements Apple2MenuView {
public enum States implements Apple2AbstractMenu.IMenuEnum {
CALIBRATE_KEYBOARD_HEIGHT_SCALE {
@Override
public String getPrefKey() {
return "portraitHeightScale";
}
@Override
public Object getPrefDefault() {
return (float) (PORTRAIT_CALIBRATE_NUM_CHOICES >> 1) / PORTRAIT_CALIBRATE_NUM_CHOICES;
}
},
CALIBRATE_FRAMEBUFFER_POSITION_SCALE {
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_VIDEO;
}
@Override
public String getPrefKey() {
return "portraitPositionScale";
}
@Override
public Object getPrefDefault() {
return 3.f / 4;
}
},
CALIBRATE_KEYBOARD_POSITION_SCALE {
@Override
public String getPrefKey() {
return "portraitPositionScale";
}
@Override
public Object getPrefDefault() {
return 0.f;
}
};
public static final int size = States.values().length;
States next() {
int ord = (ordinal() + 1) % size;
return States.values()[ord];
}
@Override
public final String getTitle(Apple2Activity activity) {
throw new RuntimeException();
}
@Override
public final String getSummary(Apple2Activity activity) {
throw new RuntimeException();
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_KEYBOARD;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
throw new RuntimeException();
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
throw new RuntimeException();
}
}
private final static String TAG = "Apple2PortraitCalibration";
public final static int PORTRAIT_CALIBRATE_NUM_CHOICES = 100;
private Apple2Activity mActivity = null;
private View mSettingsView = null;
private ArrayList<Apple2MenuView> mViewStack = null;
private int mSavedTouchDevice = Apple2SettingsMenu.TouchDeviceVariant.NONE.ordinal();
private States mStateMachine = States.CALIBRATE_KEYBOARD_HEIGHT_SCALE;
public Apple2PortraitCalibration(Apple2Activity activity, ArrayList<Apple2MenuView> viewStack) {
mActivity = activity;
mViewStack = viewStack;
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mSettingsView = inflater.inflate(R.layout.activity_calibrate_portrait, null, false);
final Button calibrateButton = (Button) mSettingsView.findViewById(R.id.button_calibrate);
final VerticalSeekBar vsb = (VerticalSeekBar) mSettingsView.findViewById(R.id.seekbar_vertical);
final int firstProgress = Math.round(Apple2Preferences.getFloatJSONPref(States.CALIBRATE_KEYBOARD_HEIGHT_SCALE) * PORTRAIT_CALIBRATE_NUM_CHOICES);
vsb.setProgress(firstProgress);
calibrateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mStateMachine = mStateMachine.next();
switch (mStateMachine) {
case CALIBRATE_KEYBOARD_HEIGHT_SCALE:
calibrateButton.setText(R.string.portrait_calibrate_keyboard_height);
vsb.setProgress(Math.round(Apple2Preferences.getFloatJSONPref(States.CALIBRATE_KEYBOARD_HEIGHT_SCALE) * PORTRAIT_CALIBRATE_NUM_CHOICES));
break;
case CALIBRATE_FRAMEBUFFER_POSITION_SCALE:
calibrateButton.setText(R.string.portrait_calibrate_framebuffer);
vsb.setProgress(Math.round(Apple2Preferences.getFloatJSONPref(States.CALIBRATE_FRAMEBUFFER_POSITION_SCALE) * PORTRAIT_CALIBRATE_NUM_CHOICES));
break;
case CALIBRATE_KEYBOARD_POSITION_SCALE:
default:
calibrateButton.setText(R.string.portrait_calibrate_keyboard_position);
vsb.setProgress(Math.round(Apple2Preferences.getFloatJSONPref(States.CALIBRATE_KEYBOARD_POSITION_SCALE) * PORTRAIT_CALIBRATE_NUM_CHOICES));
break;
}
}
});
vsb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
switch (mStateMachine) {
case CALIBRATE_KEYBOARD_HEIGHT_SCALE:
Apple2Preferences.setJSONPref(States.CALIBRATE_KEYBOARD_HEIGHT_SCALE, (float) progress / PORTRAIT_CALIBRATE_NUM_CHOICES);
break;
case CALIBRATE_FRAMEBUFFER_POSITION_SCALE:
Apple2Preferences.setJSONPref(States.CALIBRATE_FRAMEBUFFER_POSITION_SCALE, (float) progress / PORTRAIT_CALIBRATE_NUM_CHOICES);
break;
case CALIBRATE_KEYBOARD_POSITION_SCALE:
default:
Apple2Preferences.setJSONPref(States.CALIBRATE_KEYBOARD_POSITION_SCALE, (float) progress / PORTRAIT_CALIBRATE_NUM_CHOICES);
break;
}
Apple2Preferences.sync(mActivity, mStateMachine.getPrefDomain());
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
});
mSavedTouchDevice = (int) Apple2Preferences.getJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT);
Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, Apple2SettingsMenu.TouchDeviceVariant.KEYBOARD.ordinal());
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, true);
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN);
}
public final boolean isCalibrating() {
return true;
}
public void onKeyTapCalibrationEvent(char ascii, int scancode) {
}
public void show() {
if (isShowing()) {
return;
}
mActivity.pushApple2View(this);
}
public void dismiss() {
for (Apple2MenuView apple2MenuView : mViewStack) {
if (apple2MenuView != this) {
mActivity.pushApple2View(apple2MenuView);
}
}
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN, Apple2Preferences.PREF_CALIBRATING, false);
Apple2Preferences.setJSONPref(Apple2SettingsMenu.SETTINGS.CURRENT_INPUT, mSavedTouchDevice);
Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN);
mActivity.popApple2View(this);
}
public void dismissAll() {
dismiss();
}
public boolean isShowing() {
return mSettingsView.getParent() != null;
}
public View getView() {
return mSettingsView;
}
}

View File

@@ -11,15 +11,14 @@
package org.deadc0de.apple2ix;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import androidx.appcompat.app.AlertDialog;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import org.deadc0de.apple2ix.basic.BuildConfig;
import org.deadc0de.apple2ix.basic.R;
@@ -52,7 +51,30 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
if (position < 0 || position >= SETTINGS.size) {
throw new ArrayIndexOutOfBoundsException();
}
return position != SETTINGS.TOUCH_MENU_VISIBILITY.ordinal();
return true;
}
public enum TouchDeviceVariant {
NONE(0),
JOYSTICK(1),
JOYSTICK_KEYPAD(2),
KEYBOARD(3),
TOPMENU(4),
ALERT(5);
private int dev;
public static final TouchDeviceVariant FRAMEBUFFER = NONE;
public static final int size = TouchDeviceVariant.values().length;
TouchDeviceVariant(int dev) {
this.dev = dev;
}
static TouchDeviceVariant next(int ord) {
ord = (ord + 1) % size;
return TouchDeviceVariant.values()[ord];
}
}
enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
@@ -67,6 +89,21 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.input_current_summary);
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN;
}
@Override
public String getPrefKey() {
return "screenOwner";
}
@Override
public Object getPrefDefault() {
return TouchDeviceVariant.KEYBOARD.ordinal();
}
@Override
public final View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
@@ -76,6 +113,7 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
final IMenuEnum self = this;
_alertDialogHandleSelection(activity, R.string.input_current, new String[]{
activity.getResources().getString(R.string.joystick),
activity.getResources().getString(R.string.keypad),
@@ -83,16 +121,33 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
return Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(activity) - 1;
int val = (int) Apple2Preferences.getJSONPref(self);
return val - 1;
}
@Override
public void saveInt(int value) {
Apple2Preferences.CURRENT_TOUCH_DEVICE.saveTouchDevice(activity, Apple2Preferences.TouchDeviceVariant.values()[value + 1]);
Apple2Preferences.setJSONPref(self, TouchDeviceVariant.values()[value].ordinal() + 1);
}
});
}
},
VIDEO_CONFIGURE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.video_configure);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.video_configure_summary);
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
new Apple2VideoSettingsMenu(activity).show();
}
},
JOYSTICK_CONFIGURE {
@Override
public final String getTitle(Apple2Activity activity) {
@@ -106,7 +161,7 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
new Apple2JoystickSettingsMenu(activity).show();
new Apple2JoystickSettingsMenu(activity, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK).show();
}
},
KEYPAD_CONFIGURE {
@@ -122,7 +177,7 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
new Apple2KeypadSettingsMenu(activity).show();
new Apple2KeypadSettingsMenu(activity, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD).show();
}
},
KEYBOARD_CONFIGURE {
@@ -157,98 +212,6 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
new Apple2AudioSettingsMenu(activity).show();
}
},
VIDEO_CONFIGURE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.video_configure);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.video_configure_summary);
}
@Override
public View getView(Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
_addPopupIcon(activity, this, convertView);
return convertView;
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
_alertDialogHandleSelection(activity, R.string.video_configure, new String[]{
settingsMenu.mActivity.getResources().getString(R.string.color_bw),
settingsMenu.mActivity.getResources().getString(R.string.color_color),
settingsMenu.mActivity.getResources().getString(R.string.color_interpolated),
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
return Apple2Preferences.HIRES_COLOR.intValue(activity);
}
@Override
public void saveInt(int value) {
Apple2Preferences.HIRES_COLOR.saveHiresColor(settingsMenu.mActivity, Apple2Preferences.HiresColor.values()[value]);
}
});
}
},
TOUCH_MENU_ENABLED {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.touch_menu_enable);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.touch_menu_enable_summary);
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(activity));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.TOUCH_MENU_ENABLED.saveBoolean(activity, isChecked);
}
});
return convertView;
}
},
TOUCH_MENU_VISIBILITY {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.touch_menu_visibility);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.touch_menu_visibility_summary);
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() {
@Override
public void saveInt(int progress) {
Apple2Preferences.TOUCH_MENU_VISIBILITY.saveInt(activity, progress);
}
@Override
public int intValue() {
return Apple2Preferences.TOUCH_MENU_VISIBILITY.intValue(activity);
}
@Override
public void showValue(int progress, final TextView seekBarValue) {
seekBarValue.setText("" + ((float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
}
});
}
},
SHOW_DISK_OPERATIONS {
@Override
public final String getTitle(Apple2Activity activity) {
@@ -260,19 +223,81 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
return activity.getResources().getString(R.string.disk_show_operation_summary);
}
@Override
public String getPrefKey() {
return "diskAnimationsEnabled";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.SHOW_DISK_OPERATIONS.booleanValue(activity));
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.SHOW_DISK_OPERATIONS.saveBoolean(activity, isChecked);
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
}
},
FAST_DISK_OPERATIONS {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.disk_fast_operation);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.disk_fast_operation_summary);
}
@Override
public String getPrefKey() {
return "diskFastLoading";
}
@Override
public Object getPrefDefault() {
return false;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
}
},
RELEASE_NOTES {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.release_notes);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.release_notes_summary);
}
@Override
public void handleSelection(Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
activity.showReleaseNotes();
}
},
ABOUT {
@Override
public final String getTitle(Apple2Activity activity) {
@@ -286,7 +311,26 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
String url = "http://deadc0de.org/apple2ix/android/";
String url = "https://deadc0de.org/apple2ix/android/";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
activity.startActivity(i);
}
},
LICENSES {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.about_apple2ix_licenses);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.about_apple2ix_licenses_summary);
}
@Override
public void handleSelection(Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
String url = "https://deadc0de.org/apple2ix/licenses/";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
activity.startActivity(i);
@@ -305,17 +349,33 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.preferences_reset_really).setMessage(R.string.preferences_reset_warning).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
AlertDialog.Builder builder = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.preferences_reset_really).setMessage(R.string.preferences_reset_warning).setPositiveButton(R.string.reset, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
Apple2Preferences.resetPreferences(activity);
Apple2Preferences.reset(activity);
}
}).setNegativeButton(R.string.no, null);
});
AlertDialog dialog = builder.create();
activity.registerAndShowDialog(dialog);
}
},
EMAIL_LOGS {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.preferences_email_logs);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.preferences_email_logs_summary);
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
Apple2CrashHandler.getInstance().emailCrashesAndLogs(activity);
}
},
CRASH {
@Override
public final String getTitle(Apple2Activity activity) {
@@ -336,15 +396,26 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
}
}
@Override
public String getPrefKey() {
return "sendCrashReports";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
if (!BuildConfig.DEBUG) {
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.CRASH_CHECK.booleanValue(activity));
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
final IMenuEnum self = this;
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.CRASH_CHECK.saveBoolean(activity, isChecked);
Apple2Preferences.setJSONPref(self, isChecked);
}
});
}
@@ -370,7 +441,7 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "About to NPE : " + str[0].length());
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "About to NPE : " + str[0].length());
}
});
}
@@ -388,9 +459,23 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
public static final int size = SETTINGS.values().length;
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_INTERFACE;
}
@Override
public String getPrefKey() {
return null;
}
@Override
public Object getPrefDefault() {
return null;
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
/* ... */
}
@Override

View File

@@ -12,6 +12,8 @@
package org.deadc0de.apple2ix;
import android.content.Context;
import android.content.DialogInterface;
import androidx.appcompat.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
@@ -44,12 +46,19 @@ public class Apple2SplashScreen implements Apple2MenuView {
}
});
Button prefsButton = (Button) mSettingsView.findViewById(R.id.prefsButton);
Button prefsButton = (Button) mSettingsView.findViewById(R.id.resetButton);
prefsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Apple2SettingsMenu settingsMenu = mActivity.getSettingsMenu();
settingsMenu.show();
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.preferences_reset_really).setMessage(R.string.preferences_reset_warning).setPositiveButton(R.string.reset, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
Apple2Preferences.reset(mActivity);
}
});
AlertDialog dialog = builder.create();
mActivity.registerAndShowDialog(dialog);
}
});
@@ -70,7 +79,7 @@ public class Apple2SplashScreen implements Apple2MenuView {
public void run() {
Button startButton = (Button) mSettingsView.findViewById(R.id.startButton);
startButton.setEnabled(mDismissable);
Button prefsButton = (Button) mSettingsView.findViewById(R.id.prefsButton);
Button prefsButton = (Button) mSettingsView.findViewById(R.id.resetButton);
prefsButton.setEnabled(mDismissable);
Button disksButton = (Button) mSettingsView.findViewById(R.id.disksButton);
disksButton.setEnabled(mDismissable);

View File

@@ -0,0 +1,613 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2016 Aaron Culliney
*
*/
package org.deadc0de.apple2ix;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import org.deadc0de.apple2ix.basic.BuildConfig;
import org.deadc0de.apple2ix.basic.R;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Apple2Utils {
public final static String TAG = "Apple2Utils";
private static String sDataDir = null;
private static File sExternalFilesDir = null;
private static File sRealExternalFilesDir = null;
public static boolean readEntireFile(File file, StringBuilder fileData) {
final int maxAttempts = 5;
int attempts = 0;
do {
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
char[] buf = new char[1024];
int numRead = 0;
while ((numRead = reader.read(buf)) != -1) {
String readData = String.valueOf(buf, 0, numRead);
fileData.append(readData);
}
reader.close();
break;
} catch (InterruptedIOException ie) {
/* EINTR, EAGAIN ... */
} catch (IOException e) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Error reading file at path : " + file.toString());
}
try {
Thread.sleep(100, 0);
} catch (InterruptedException e) {
/* ... */
}
++attempts;
} while (attempts < maxAttempts);
return attempts < maxAttempts;
}
public static boolean writeFile(final StringBuilder data, File file) {
final int maxAttempts = 5;
int attempts = 0;
do {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
writer.append(data);
writer.flush();
writer.close();
break;
} catch (InterruptedIOException ie) {
/* EINTR, EAGAIN ... */
} catch (IOException e) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Exception attempting to write data : " + e);
}
try {
Thread.sleep(100, 0);
} catch (InterruptedException e) {
/* ... */
}
++attempts;
} while (attempts < maxAttempts);
return attempts < maxAttempts;
}
public static File zipFiles(File[] files, File zipFile) {
zipFile.delete();
ZipOutputStream out = null;
do {
try {
zipFile.createNewFile();
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could not create zipfile " + zipFile.getAbsolutePath() + " : " + ioe.getMessage());
break;
}
final int BUF_SIZ = 4096;
BufferedInputStream origin = null;
try {
out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could not create zip outputStream : " + ioe.getMessage());
break;
}
byte data[] = new byte[BUF_SIZ];
for (File file : files) {
FileInputStream fi = null;
try {
fi = new FileInputStream(file);
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could not create file input stream : " + ioe.getMessage());
continue;
}
origin = new BufferedInputStream(fi, BUF_SIZ);
ZipEntry entry = new ZipEntry(file.getName());
try {
out.putNextEntry(entry);
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could not put next zip entry : " + ioe.getMessage());
continue;
}
final int maxAttempts = 5;
int attempts = 0;
do {
int count;
try {
while ((count = origin.read(data, 0, BUF_SIZ)) != -1) {
out.write(data, 0, count);
}
break;
} catch (InterruptedIOException ie) {
/* EINTR, EAGAIN ... */
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could read/write zip data : " + ioe.getMessage());
break;
}
++attempts;
} while (attempts < maxAttempts);
try {
origin.close();
} catch (IOException ioe) {
// ...
}
}
} while (false);
if (out != null) {
try {
out.close();
} catch (IOException ioe) {
// ...
}
}
if (zipFile.exists()) {
return zipFile;
}
return null;
}
public static void migrateToExternalStorage(Apple2Activity activity) {
do {
if (BuildConfig.VERSION_CODE >= 18) {
// Rename old emulator state file
// TODO FIXME : Remove this migration code when all/most users are on version >= 18
final File srcFile = new File(getDataDir(activity) + File.separator + Apple2MainMenu.OLD_SAVE_FILE);
if (!srcFile.exists()) {
break;
}
final File dstFile = new File(getDataDir(activity) + File.separator + Apple2MainMenu.SAVE_FILE);
final boolean success = copyFile(srcFile, dstFile);
if (success) {
srcFile.delete();
}
}
} while (false);
final File extStorage = Apple2Utils.getExternalStorageDirectory(activity);
if (extStorage == null) {
return;
}
do {
if (BuildConfig.VERSION_CODE >= 18) {
// Migrate old emulator state file from internal path to external storage to allow user manipulation
// TODO FIXME : Remove this migration code when all/most users are on version >= 18
final File srcFile = new File(getDataDir(activity) + File.separator + Apple2MainMenu.SAVE_FILE);
if (!srcFile.exists()) {
break;
}
final File dstFile = new File(extStorage + File.separator + Apple2MainMenu.SAVE_FILE);
final boolean success = copyFile(srcFile, dstFile);
if (success) {
srcFile.delete();
}
}
} while (false);
do {
if (BuildConfig.VERSION_CODE >= 20) {
// Recursively rename all *.state files found in /sdcard/apple2ix
// TODO FIXME : Remove this migration code when all/most users are on version >= 20
recursivelyRenameEmulatorStateFiles(extStorage);
}
} while (false);
}
public static File getExternalStorageDirectory(Apple2Activity activity) {
do {
if (sExternalFilesDir != null) {
break;
}
String storageState = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(storageState)) {
// 2015/10/28 : do not expose sExternalFilesDir unless it is writable
break;
}
File realExternalStorageDir = Environment.getExternalStorageDirectory();
if (realExternalStorageDir == null) {
break;
}
File externalDir = new File(realExternalStorageDir, "apple2ix"); // /sdcard/apple2ix
if (!externalDir.exists()) {
boolean made = externalDir.mkdirs();
if (!made) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "WARNING: could not make directory : " + sExternalFilesDir);
break;
}
}
sExternalFilesDir = externalDir;
sRealExternalFilesDir = realExternalStorageDir;
} while (false);
return sExternalFilesDir;
}
public static File getRealExternalStorageDirectory(Apple2Activity activity) {
getExternalStorageDirectory(activity);
return sRealExternalFilesDir;
}
public static boolean isExternalStorageAccessible(Apple2Activity activity) {
getExternalStorageDirectory(activity);
return (sRealExternalFilesDir != null) && (new File(sRealExternalFilesDir.getAbsolutePath()).listFiles() != null);
}
// HACK NOTE 2015/02/22 : Apparently native code cannot easily access stuff in the APK ... so copy various resources
// out of the APK and into the /data/data/... for ease of access. Because this is FOSS software we don't care about
// security or DRM for these assets =)
public static String getDataDir(Apple2Activity activity) {
if (sDataDir != null) {
return sDataDir;
}
try {
PackageManager pm = activity.getPackageManager();
PackageInfo pi = pm.getPackageInfo(activity.getPackageName(), 0);
sDataDir = pi.applicationInfo.dataDir;
} catch (PackageManager.NameNotFoundException e) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "" + e);
if (sDataDir == null) {
sDataDir = "/data/local/tmp";
}
}
return sDataDir;
}
public static void exposeAPKAssetsToExternal(final Apple2Activity activity) {
getExternalStorageDirectory(activity);
if (sExternalFilesDir == null) {
return;
}
final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
bar.setVisibility(View.VISIBLE);
bar.setIndeterminate(true);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
Log.v(TAG, "Overwriting system files in /sdcard/apple2ix/ (external storage) ...");
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/sExternalFilesDir.getAbsolutePath(), false);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
bar.setVisibility(View.INVISIBLE);
bar.setIndeterminate(false);
} catch (NullPointerException npe) {
Log.v(TAG, "Avoid NPE in exposeAPKAssetsToExternal #2");
}
}
});
}
});
} catch (NullPointerException npe) {
Log.v(TAG, "Avoid NPE in exposeAPKAssetsToExternal #1");
}
}
});
}
public static void exposeAPKAssets(final Apple2Activity activity) {
final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
bar.setVisibility(View.VISIBLE);
bar.setIndeterminate(true);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
getDataDir(activity);
// FIXME TODO : Heavy-handed migration to 1.1.3 ...
recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "blanks"));
recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "demo"));
recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "eamon"));
recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "logo"));
recursivelyDelete(new File(new File(sDataDir, "disks").getAbsolutePath(), "miscgame"));
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "First time copying stuff-n-things out of APK for ease-of-NDK access...");
getExternalStorageDirectory(activity);
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"disks", /*to location:*/new File(sDataDir, "disks").getAbsolutePath(), true);
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/new File(sDataDir, "keyboards").getAbsolutePath(), false);
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"shaders", /*to location:*/new File(sDataDir, "shaders").getAbsolutePath(), false);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
bar.setVisibility(View.INVISIBLE);
bar.setIndeterminate(false);
} catch (NullPointerException npe) {
Log.v(TAG, "Avoid NPE in exposeAPKAssets #1");
}
}
});
}
});
} catch (NullPointerException npe) {
Log.v(TAG, "Avoid NPE in exposeAPKAssets #1");
}
}
});
}
public static void exposeSymbols(Apple2Activity activity) {
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"symbols", /*to location:*/new File(sDataDir, "symbols").getAbsolutePath(), false);
}
public static void unexposeSymbols(Apple2Activity activity) {
recursivelyDelete(new File(sDataDir, "symbols"));
}
// TODO FIXME : WARNING : this is super dangerous if there are symlinks !!!
private static void recursivelyDelete(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File f : files) {
recursivelyDelete(f);
}
}
}
if (!file.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Failed to delete file: " + file);
}
}
private static void recursivelyCopyAPKAssets(Apple2Activity activity, String srcFileOrDir, String dstFileOrDir, boolean shouldGzip) {
AssetManager assetManager = activity.getAssets();
final int maxAttempts = 5;
String[] files = null;
int attempts = 0;
do {
try {
files = assetManager.list(srcFileOrDir);
break;
} catch (InterruptedIOException e) {
/* EINTR, EAGAIN ... */
} catch (IOException e) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "OOPS exception attempting to list APK files at : " + srcFileOrDir + " : " + e);
}
try {
Thread.sleep(100, 0);
} catch (InterruptedException ie) {
/* ... */
}
++attempts;
} while (attempts < maxAttempts);
if (files == null) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "OOPS, could not list APK assets at : " + srcFileOrDir);
return;
}
if (files.length > 0) {
// ensure destination directory exists
File dstPath = new File(dstFileOrDir);
if (!dstPath.mkdirs()) {
if (!dstPath.exists()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "OOPS, could not mkdirs on " + dstPath);
return;
}
}
for (String filename : files) {
// iterate on files and subdirectories
recursivelyCopyAPKAssets(activity, srcFileOrDir + File.separator + filename, dstFileOrDir + File.separator + filename, shouldGzip);
}
return;
}
// presumably this is a file, not a subdirectory
InputStream is = null;
OutputStream os = null;
attempts = 0;
do {
try {
is = assetManager.open(srcFileOrDir);
if (shouldGzip) {
os = new GZIPOutputStream(new FileOutputStream(dstFileOrDir + ".gz"));
} else {
os = new FileOutputStream(dstFileOrDir);
}
copyFile(is, os);
break;
} catch (InterruptedIOException e) {
/* EINTR, EAGAIN */
} catch (IOException e) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Failed to copy asset file: " + srcFileOrDir + " : " + e.getMessage());
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
// NOOP
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
// NOOP
}
}
}
try {
Thread.sleep(100, 0);
} catch (InterruptedException ie) {
/* ... */
}
++attempts;
} while (attempts < maxAttempts);
}
private static void recursivelyRenameEmulatorStateFiles(File directory) {
try {
if (!directory.isDirectory()) {
return;
}
final int oldSuffixLen = 6;
File[] files = directory.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if (name.equals(".") || name.equals("..")) {
return false;
}
final File file = new File(dir, name);
if (file.isDirectory()) {
return true;
}
final int len = name.length();
if (len < oldSuffixLen) {
return false;
}
final String suffix = name.substring(len - oldSuffixLen, len);
return suffix.equalsIgnoreCase(".state");
}
});
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
recursivelyRenameEmulatorStateFiles(file);
} else {
final File srcFile = file;
final String oldName = file.getName();
final String newName = oldName.substring(0, oldName.length() - oldSuffixLen) + Apple2MainMenu.SAVE_FILE_EXTENSION;
boolean success = file.renameTo(new File(file.getParentFile(), newName));
if (success) {
srcFile.delete();
}
}
}
} catch (Exception e) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS : {e}");
}
}
private static boolean copyFile(final File srcFile, final File dstFile) {
final int maxAttempts = 5;
int attempts = 0;
do {
try {
FileInputStream is = new FileInputStream(srcFile);
FileOutputStream os = new FileOutputStream(dstFile);
copyFile(is, os);
break;
} catch (InterruptedIOException e) {
// EINTR, EAGAIN ...
} catch (IOException e) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "OOPS exception attempting to copy emulator state file : " + e);
}
try {
Thread.sleep(100, 0);
} catch (InterruptedException ie) {
// ...
}
++attempts;
} while (attempts < maxAttempts);
return attempts < maxAttempts;
}
private static void copyFile(InputStream is, OutputStream os) throws IOException {
final int BUF_SZ = 4096;
byte[] buf = new byte[BUF_SZ];
while (true) {
int len = is.read(buf, 0, BUF_SZ);
if (len < 0) {
break;
}
os.write(buf, 0, len);
}
os.flush();
}
}

View File

@@ -0,0 +1,441 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2015 Aaron Culliney
*
*/
package org.deadc0de.apple2ix;
import android.content.pm.ActivityInfo;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import java.util.ArrayList;
import org.deadc0de.apple2ix.basic.R;
public class Apple2VideoSettingsMenu extends Apple2AbstractMenu {
private final static String TAG = "Apple2VideoSettingsMenu";
public Apple2VideoSettingsMenu(Apple2Activity activity) {
super(activity);
}
@Override
public final String[] allTitles() {
return SETTINGS.titles(mActivity);
}
@Override
public final IMenuEnum[] allValues() {
return SETTINGS.values();
}
@Override
public final boolean areAllItemsEnabled() {
return true;
}
@Override
public final boolean isEnabled(int position) {
if (position < 0 || position >= SETTINGS.size) {
throw new ArrayIndexOutOfBoundsException();
}
if (position == SETTINGS.PORTRAIT_CALIBRATE.ordinal()) {
if ((boolean) Apple2Preferences.getJSONPref(SETTINGS.LANDSCAPE_MODE)) {
return false;
}
}
return true;
}
public enum ColorMode {
COLOR_MODE_MONO,
COLOR_MODE_COLOR,
COLOR_MODE_INTERP,
COLOR_MODE_COLOR_MONITOR,
COLOR_MODE_MONO_TV,
COLOR_MODE_COLOR_TV,
}
public enum MonoMode {
MONO_MODE_BW,
MONO_MODE_GREEN,
}
// must match interface_colorscheme_t
public enum DeviceColor {
GREEN_ON_BLACK(0),
GREEN_ON_BLUE(1), // ...
RED_ON_BLACK(2),
BLUE_ON_BLACK(3),
WHITE_ON_BLACK(4);
private int val;
DeviceColor(int val) {
this.val = val;
}
}
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
LANDSCAPE_MODE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.mode_landscape);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.mode_landscape_summary);
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_INTERFACE;
}
@Override
public String getPrefKey() {
return "landscapeEnabled";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public final View getView(final Apple2Activity activity, View convertView) {
final Object self = this;
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref((IMenuEnum) self, isChecked);
applyLandscapeMode(activity);
}
});
return convertView;
}
},
PORTRAIT_CALIBRATE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.portrait_calibrate);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.portrait_calibrate_summary);
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
ArrayList<Apple2MenuView> viewStack = new ArrayList<Apple2MenuView>();
{
int idx = 0;
while (true) {
Apple2MenuView apple2MenuView = activity.peekApple2View(idx);
if (apple2MenuView == null) {
break;
}
viewStack.add(apple2MenuView);
++idx;
}
}
// switch to portrait
Apple2Preferences.setJSONPref(SETTINGS.LANDSCAPE_MODE, false);
applyLandscapeMode(activity);
Apple2PortraitCalibration calibration = new Apple2PortraitCalibration(activity, viewStack);
// show this new view...
calibration.show();
// ...with nothing else underneath 'cept the emulator OpenGL layer
for (Apple2MenuView apple2MenuView : viewStack) {
activity.popApple2View(apple2MenuView);
}
}
},
COLOR_MODE_CONFIGURE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.color_configure);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.color_configure_summary);
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_VIDEO;
}
@Override
public String getPrefKey() {
return "colorMode";
}
@Override
public Object getPrefDefault() {
return ColorMode.COLOR_MODE_COLOR_TV.ordinal();
}
@Override
public View getView(Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
_addPopupIcon(activity, this, convertView);
return convertView;
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
final Apple2AbstractMenu.IMenuEnum self = this;
_alertDialogHandleSelection(activity, R.string.video_configure, new String[]{
settingsMenu.mActivity.getResources().getString(R.string.color_mono),
settingsMenu.mActivity.getResources().getString(R.string.color_color),
settingsMenu.mActivity.getResources().getString(R.string.color_interpolated),
settingsMenu.mActivity.getResources().getString(R.string.color_monitor),
settingsMenu.mActivity.getResources().getString(R.string.color_tv_mono),
settingsMenu.mActivity.getResources().getString(R.string.color_tv),
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
public void saveInt(int value) {
Apple2Preferences.setJSONPref(self, ColorMode.values()[value].ordinal());
}
});
}
},
SHOW_HALF_SCANLINES {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.show_half_scanlines);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.show_half_scanlines_summary);
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_VIDEO;
}
@Override
public String getPrefKey() {
return "showHalfScanlines";
}
@Override
public Object getPrefDefault() {
return true;
}
@Override
public View getView(final Apple2Activity activity, View convertView) {
final IMenuEnum self = this;
convertView = _basicView(activity, this, convertView);
CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this));
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Apple2Preferences.setJSONPref(self, isChecked);
}
});
return convertView;
}
},
MONO_MODE_CONFIGURE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.mono_configure);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.mono_configure_summary);
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_VIDEO;
}
@Override
public String getPrefKey() {
return "monoMode";
}
@Override
public Object getPrefDefault() {
return MonoMode.MONO_MODE_BW.ordinal();
}
@Override
public View getView(Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
_addPopupIcon(activity, this, convertView);
return convertView;
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
final Apple2AbstractMenu.IMenuEnum self = this;
_alertDialogHandleSelection(activity, R.string.video_configure, new String[]{
settingsMenu.mActivity.getResources().getString(R.string.color_bw),
settingsMenu.mActivity.getResources().getString(R.string.color_green),
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
return (int) Apple2Preferences.getJSONPref(self);
}
@Override
public void saveInt(int value) {
Apple2Preferences.setJSONPref(self, MonoMode.values()[value].ordinal());
}
});
}
},
COLOR_DEVICE_CONFIGURE {
@Override
public final String getTitle(Apple2Activity activity) {
return activity.getResources().getString(R.string.touch_device_color);
}
@Override
public final String getSummary(Apple2Activity activity) {
return activity.getResources().getString(R.string.touch_device_color_summary);
}
@Override
public String getPrefDomain() {
return Apple2Preferences.PREF_DOMAIN_INTERFACE;
}
@Override
public String getPrefKey() {
return "hudColorMode";
}
@Override
public Object getPrefDefault() {
return DeviceColor.RED_ON_BLACK.ordinal();
}
@Override
public View getView(Apple2Activity activity, View convertView) {
convertView = _basicView(activity, this, convertView);
_addPopupIcon(activity, this, convertView);
return convertView;
}
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
final Apple2AbstractMenu.IMenuEnum self = this;
_alertDialogHandleSelection(activity, R.string.touch_device_color_configure, new String[]{
settingsMenu.mActivity.getResources().getString(R.string.color_red_on_black),
settingsMenu.mActivity.getResources().getString(R.string.color_green_on_black),
settingsMenu.mActivity.getResources().getString(R.string.color_blue_on_black),
settingsMenu.mActivity.getResources().getString(R.string.color_white_on_black),
}, new IPreferenceLoadSave() {
@Override
public int intValue() {
int colorscheme = (int) Apple2Preferences.getJSONPref(self);
if (colorscheme == DeviceColor.GREEN_ON_BLACK.ordinal()) {
return 1;
} else if (colorscheme == DeviceColor.BLUE_ON_BLACK.ordinal()) {
return 2;
} else if (colorscheme == DeviceColor.WHITE_ON_BLACK.ordinal()) {
return 3;
} else {
return 0;
}
}
@Override
public void saveInt(int value) {
switch (value) {
case 1:
Apple2Preferences.setJSONPref(self, (int) DeviceColor.GREEN_ON_BLACK.ordinal());
break;
case 2:
Apple2Preferences.setJSONPref(self, (int) DeviceColor.BLUE_ON_BLACK.ordinal());
break;
case 3:
Apple2Preferences.setJSONPref(self, (int) DeviceColor.WHITE_ON_BLACK.ordinal());
break;
default:
Apple2Preferences.setJSONPref(self, (int) DeviceColor.RED_ON_BLACK.ordinal());
break;
}
}
});
}
};
public static final int size = SETTINGS.values().length;
@Override
public String getPrefDomain() {
return null;
}
@Override
public String getPrefKey() {
return null;
}
@Override
public Object getPrefDefault() {
return null;
}
@Override
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
}
@Override
public View getView(Apple2Activity activity, View convertView) {
return _basicView(activity, this, convertView);
}
public static boolean applyLandscapeMode(final Apple2Activity activity) {
if ((boolean) Apple2Preferences.getJSONPref(SETTINGS.LANDSCAPE_MODE)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
return false;
} else {
int orientation = activity.getRequestedOrientation();
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
return orientation != ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
}
}
public static String[] titles(Apple2Activity activity) {
String[] titles = new String[size];
int i = 0;
for (SETTINGS setting : values()) {
titles[i++] = setting.getTitle(activity);
}
return titles;
}
}
}

View File

@@ -15,12 +15,20 @@
package org.deadc0de.apple2ix;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.media.AudioManager;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.Build;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import com.example.inputmanagercompat.InputManagerCompat;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
@@ -28,41 +36,58 @@ import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.opengles.GL10;
/**
* A simple GLSurfaceView sub-class that demonstrate how to perform
* OpenGL ES 2.0 rendering into a GL Surface. Note the following important
* details:
*
* - The class must use a custom context factory to enable 2.0 rendering.
* See ContextFactory class definition below.
*
* - The class must use a custom EGLConfigChooser to be able to select
* an EGLConfig that supports 2.0. This is done by providing a config
* specification to eglChooseConfig() that has the attribute
* EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag
* set. See ConfigChooser class definition below.
*
* - The class must select the surface's format, then choose an EGLConfig
* that matches it exactly (with regards to red/green/blue/alpha channels
* bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
*/
class Apple2View extends GLSurfaceView {
class Apple2View extends GLSurfaceView implements InputManagerCompat.InputDeviceListener {
private final static String TAG = "Apple2View";
private final static boolean DEBUG = false;
private final static int MAX_FINGERS = 32;// HACK ...
private Apple2Activity mActivity = null;
private Runnable mGraphicsInitializedRunnable = null;
public final static long NATIVE_TOUCH_HANDLED = (1 << 0);
public final static long NATIVE_TOUCH_REQUEST_SHOW_MENU = (1 << 1);
public final static long NATIVE_TOUCH_REQUEST_SHOW_SYSTEM_KBD = (1 << 2);
private static native void nativeGraphicsInitialized(int width, int height);
public final static long NATIVE_TOUCH_KEY_TAP = (1 << 4);
public final static long NATIVE_TOUCH_KBD = (1 << 5);
public final static long NATIVE_TOUCH_JOY = (1 << 6);
public final static long NATIVE_TOUCH_MENU = (1 << 7);
public final static long NATIVE_TOUCH_JOY_KPAD = (1 << 8);
private static native void nativeGraphicsChanged(int width, int height);
public final static long NATIVE_TOUCH_INPUT_DEVICE_CHANGED = (1 << 16);
public final static long NATIVE_TOUCH_ASCII_SCANCODE_SHIFT = 32;
public final static long NATIVE_TOUCH_ASCII_SCANCODE_MASK = 0xFFFFL;
public final static long NATIVE_TOUCH_ASCII_MASK = 0xFF00L;
public final static long NATIVE_TOUCH_SCANCODE_MASK = 0x00FFL;
private Apple2Activity mActivity;
private final InputManagerCompat mInputManager;
private float[] mXCoords = new float[MAX_FINGERS];
private float[] mYCoords = new float[MAX_FINGERS];
private int mWidth = 0;
private int mHeight = 0;
private static native void nativeGraphicsInitialized();
private static native void nativeRender();
public Apple2View(Apple2Activity activity, Runnable graphicsInitializedRunnable) {
private static native void nativeOnJoystickMove(int x, int y);
public static native long nativeOnTouch(int action, int pointerCount, int pointerIndex, float[] xCoords, float[] yCoords);
public Apple2View(Apple2Activity activity) {
super(activity.getApplication());
mActivity = activity;
mGraphicsInitializedRunnable = graphicsInitializedRunnable;
setFocusable(true);
setFocusableInTouchMode(true);
mInputManager = InputManagerCompat.Factory.getInputManager(this.getContext());
if (mInputManager != null) {
mInputManager.registerInputDeviceListener(this, null);
}
/* By default, GLSurfaceView() creates a RGB_565 opaque surface.
* If we want a translucent one, we should change the surface's
@@ -96,13 +121,12 @@ class Apple2View extends GLSurfaceView {
Apple2View.this.getWindowVisibleDisplayFrame(rect);
int h = rect.height();
int w = rect.width();
if (w < h) {
// assure landscape dimensions
final int w_ = w;
w = h;
h = w_;
if (w != mWidth || h != mHeight) {
mWidth = w;
mHeight = h;
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_DEVICE_WIDTH, mWidth);
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_DEVICE_HEIGHT, mHeight);
}
nativeGraphicsChanged(w, h);
}
});
@@ -112,9 +136,9 @@ class Apple2View extends GLSurfaceView {
private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
Log.w(TAG, "creating OpenGL ES 2.0 context");
Apple2Activity.logMessage(Apple2Activity.LogType.WARN, TAG, "creating OpenGL ES 2.0 context");
checkEglError("Before eglCreateContext", egl);
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
checkEglError("After eglCreateContext", egl);
return context;
@@ -128,7 +152,7 @@ class Apple2View extends GLSurfaceView {
private static void checkEglError(String prompt, EGL10 egl) {
int error;
while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, String.format("%s: EGL error: 0x%x", prompt, error));
}
}
@@ -149,12 +173,12 @@ class Apple2View extends GLSurfaceView {
*/
private static int EGL_OPENGL_ES2_BIT = 4;
private static int[] s_configAttribs2 = {
EGL10.EGL_RED_SIZE, 4,
EGL10.EGL_GREEN_SIZE, 4,
EGL10.EGL_BLUE_SIZE, 4,
EGL10.EGL_ALPHA_SIZE, 4,
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_NONE
EGL10.EGL_RED_SIZE, 4,
EGL10.EGL_GREEN_SIZE, 4,
EGL10.EGL_BLUE_SIZE, 4,
EGL10.EGL_ALPHA_SIZE, 4,
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_NONE
};
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
@@ -174,15 +198,15 @@ class Apple2View extends GLSurfaceView {
egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
if (DEBUG) {
printConfigs(egl, display, configs);
printConfigs(egl, display, configs);
}
// Now return the "best" one
EGLConfig best = chooseConfig(egl, display, configs);
if (best == null) {
Log.e(TAG, "OOPS! Did not pick an EGLConfig. What device are you using?! Android will now crash this app...");
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS! Did not pick an EGLConfig. What device are you using?! Android will now crash this app...");
} else {
Log.w(TAG, "Using EGL CONFIG : ");
Apple2Activity.logMessage(Apple2Activity.LogType.WARN, TAG, "Using EGL CONFIG : ");
printConfig(egl, display, best);
}
return best;
@@ -221,9 +245,9 @@ class Apple2View extends GLSurfaceView {
private void printConfigs(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
int numConfigs = configs.length;
Log.w(TAG, String.format("%d configurations", numConfigs));
Apple2Activity.logMessage(Apple2Activity.LogType.WARN, TAG, String.format("%d configurations", numConfigs));
for (int i = 0; i < numConfigs; i++) {
Log.w(TAG, String.format("Configuration %d:\n", i));
Apple2Activity.logMessage(Apple2Activity.LogType.WARN, TAG, String.format("Configuration %d:\n", i));
printConfig(egl, display, configs[i]);
}
}
@@ -303,11 +327,11 @@ class Apple2View extends GLSurfaceView {
for (int i = 0; i < attributes.length; i++) {
int attribute = attributes[i];
String name = names[i];
if ( egl.eglGetConfigAttrib(display, config, attribute, value)) {
Log.w(TAG, String.format(" %s: %d\n", name, value[0]));
if (egl.eglGetConfigAttrib(display, config, attribute, value)) {
Apple2Activity.logMessage(Apple2Activity.LogType.WARN, TAG, String.format(" %s: %d\n", name, value[0]));
} else {
// Log.w(TAG, String.format(" %s: failed\n", name));
while (egl.eglGetError() != EGL10.EGL_SUCCESS);
// Apple2Activity.logMessage(Apple2Activity.LogType.WARN, TAG, String.format(" %s: failed\n", name));
while (egl.eglGetError() != EGL10.EGL_SUCCESS) ;
}
}
}
@@ -331,27 +355,19 @@ class Apple2View extends GLSurfaceView {
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Apple2Preferences.GL_VENDOR.saveString(mActivity, GLES20.glGetString(GLES20.GL_VENDOR));
Apple2Preferences.GL_RENDERER.saveString(mActivity, GLES20.glGetString(GLES20.GL_RENDERER));
Apple2Preferences.GL_VERSION.saveString(mActivity, GLES20.glGetString(GLES20.GL_VERSION));
Apple2Preferences.setJSONPref(Apple2CrashHandler.SETTINGS.GL_VENDOR, GLES20.glGetString(GLES20.GL_VENDOR));
Apple2Preferences.setJSONPref(Apple2CrashHandler.SETTINGS.GL_RENDERER, GLES20.glGetString(GLES20.GL_RENDERER));
Apple2Preferences.setJSONPref(Apple2CrashHandler.SETTINGS.GL_VERSION, GLES20.glGetString(GLES20.GL_VERSION));
Log.v(TAG, "graphicsInitialized(" + width + ", " + height + ")");
if (width < height) {
// assure landscape dimensions
final int w_ = width;
width = height;
height = w_;
}
Apple2View.this.mWidth = width;
Apple2View.this.mHeight = height;
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_DEVICE_WIDTH, mWidth);
Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_INTERFACE, Apple2Preferences.PREF_DEVICE_HEIGHT, mHeight);
nativeGraphicsInitialized();
nativeGraphicsInitialized(width, height);
if (Apple2View.this.mGraphicsInitializedRunnable != null) {
Apple2View.this.mGraphicsInitializedRunnable.run();
Apple2View.this.mGraphicsInitializedRunnable = null;
}
Apple2View.this.mActivity.maybeResumeCPU();
Apple2View.this.mActivity.maybeResumeEmulation();
}
@Override
@@ -359,4 +375,164 @@ class Apple2View extends GLSurfaceView {
// Do nothing.
}
}
// --------------------------------------------------------------------------
// Event handling, touch, keyboard, gamepad
@Override
public void onInputDeviceAdded(int deviceId) {
}
@Override
public void onInputDeviceChanged(int deviceId) {
}
@Override
public void onInputDeviceRemoved(int deviceId) {
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return super.onGenericMotionEvent(event);
}
if (mActivity.isEmulationPaused()) {
return super.onGenericMotionEvent(event);
}
// Check that the event came from a joystick or gamepad since a generic
// motion event could be almost anything.
int eventSource = event.getSource();
if ((event.getAction() == MotionEvent.ACTION_MOVE) && (((eventSource & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || ((eventSource & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK))) {
int id = event.getDeviceId();
if (id != -1) {
InputDevice device = event.getDevice();
float x = getCenteredAxis(event, device, MotionEvent.AXIS_X);
if (x == 0) {
x = getCenteredAxis(event, device, MotionEvent.AXIS_HAT_X);
}
if (x == 0) {
x = getCenteredAxis(event, device, MotionEvent.AXIS_Z);
}
float y = getCenteredAxis(event, device, MotionEvent.AXIS_Y);
if (y == 0) {
y = getCenteredAxis(event, device, MotionEvent.AXIS_HAT_Y);
}
if (y == 0) {
y = getCenteredAxis(event, device, MotionEvent.AXIS_RZ);
}
int normal_x = (int) ((x + 1.f) * 128.f);
if (normal_x < 0) {
normal_x = 0;
}
if (normal_x > 255) {
normal_x = 255;
}
int normal_y = (int) ((y + 1.f) * 128.f);
if (normal_y < 0) {
normal_y = 0;
}
if (normal_y > 255) {
normal_y = 255;
}
nativeOnJoystickMove(normal_x, normal_y);
return true;
}
}
return super.onGenericMotionEvent(event);
}
private static float getCenteredAxis(MotionEvent event, InputDevice device, int axis) {
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return 0;
}
final InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource());
if (range != null) {
final float flat = range.getFlat();
final float value = event.getAxisValue(axis);
// Ignore axis values that are within the 'flat' region of the joystick axis center.
// A joystick at rest does not always report an absolute position of (0,0).
if (Math.abs(value) > flat) {
return value;
}
}
return 0;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
do {
if (Apple2Activity.isNativeBarfed()) {
break;
}
if (mActivity.getMainMenu() == null) {
break;
}
Apple2MenuView apple2MenuView = mActivity.peekApple2View();
if ((apple2MenuView != null) && (!apple2MenuView.isCalibrating())) {
break;
}
//printSamples(event);
int action = event.getActionMasked();
int pointerIndex = event.getActionIndex();
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount/* && i < MAX_FINGERS */; i++) {
mXCoords[i] = event.getX(i);
mYCoords[i] = event.getY(i);
}
long nativeFlags = nativeOnTouch(action, pointerCount, pointerIndex, mXCoords, mYCoords);
if ((nativeFlags & NATIVE_TOUCH_HANDLED) == 0) {
break;
}
if ((nativeFlags & NATIVE_TOUCH_REQUEST_SHOW_MENU) != 0) {
mActivity.getMainMenu().show();
}
if ((nativeFlags & NATIVE_TOUCH_KEY_TAP) != 0) {
if ((boolean) Apple2Preferences.getJSONPref(Apple2KeyboardSettingsMenu.SETTINGS.KEYBOARD_ENABLE_CLICK)) {
AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
if (am != null) {
am.playSoundEffect(AudioManager.FX_KEY_CLICK);
}
}
if ((apple2MenuView != null) && apple2MenuView.isCalibrating()) {
long asciiScancodeLong = nativeFlags & (NATIVE_TOUCH_ASCII_SCANCODE_MASK << NATIVE_TOUCH_ASCII_SCANCODE_SHIFT);
int asciiInt = (int) (asciiScancodeLong >> (NATIVE_TOUCH_ASCII_SCANCODE_SHIFT + 8));
int scancode = (int) ((asciiScancodeLong >> NATIVE_TOUCH_ASCII_SCANCODE_SHIFT) & 0xFFL);
char ascii = (char) asciiInt;
apple2MenuView.onKeyTapCalibrationEvent(ascii, scancode);
}
}
if ((nativeFlags & NATIVE_TOUCH_REQUEST_SHOW_SYSTEM_KBD) != 0) {
clearFocus();
requestFocus();
InputMethodManager inputMethodManager = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_FORCED);
}
}
} while (false);
return true;
}
}

View File

@@ -17,7 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:minHeight="?attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize">

View File

@@ -2,7 +2,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/black"
android:baselineAligned="false"
android:orientation="vertical">
@@ -16,6 +15,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="40dp"
android:layout_marginLeft="40dp"
android:text="@string/diskA" />
<RadioButton
@@ -37,6 +38,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="40dp"
android:layout_marginLeft="40dp"
android:text="@string/disk_read_only" />
<RadioButton

View File

@@ -17,7 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:minHeight="?attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize">
@@ -43,6 +43,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/a2preference_title"
android:layout_alignLeft="@id/a2preference_title"
android:layout_alignStart="@id/a2preference_title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:maxLines="4" />

View File

@@ -3,7 +3,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:minHeight="?attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize">

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:baselineAligned="false"
android:orientation="vertical">
<RadioGroup
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/radioButton_openApple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="40dp"
android:layout_marginLeft="40dp"
android:text="@string/reboot" />
<RadioButton
android:id="@+id/radioButton_closedApple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/reset_self_test" />
<RadioButton
android:id="@+id/radioButton_noApple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/reset_soft" />
</RadioGroup>
</LinearLayout>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="0dp"
android:layout_marginStart="0dp"
android:layout_marginRight="0dp"
@@ -20,4 +20,19 @@
android:layout_alignParentStart="true"
android:layout_marginLeft="0dp"
android:layout_marginStart="0dp" />
<TextView
android:id="@+id/axisCoords"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/seekBar"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="@dimen/preference_margin_left"
android:layout_marginLeft="@dimen/preference_margin_left"
android:layout_marginTop="@dimen/preference_margin_top"
android:background="@color/white"
android:text="X:255 Y:255"
android:textColor="@color/black" />
</RelativeLayout>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/calibratePortrait"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/preference_margin_bottom"
android:layout_marginEnd="@dimen/preference_margin_right"
android:layout_marginLeft="@dimen/preference_margin_left"
android:layout_marginRight="@dimen/preference_margin_right"
android:layout_marginStart="@dimen/preference_margin_left"
android:layout_marginTop="@dimen/preference_margin_top"
android:layout_weight="1">
<android.widget.VerticalSeekBar
android:id="@+id/seekbar_vertical"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_alignTop="@+id/button_calibrate" />
<Button
android:id="@+id/button_calibrate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/portrait_calibrate_keyboard_height"
android:layout_marginTop="22dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>

View File

@@ -20,11 +20,26 @@
android:layout_marginLeft="@dimen/preference_margin_left"
android:layout_marginStart="@dimen/preference_margin_left"
android:layout_marginTop="@dimen/preference_margin_top"
android:background="@color/white"
android:textColor="@color/black"
android:text="@string/keypad_choose_current" />
<Button
android:id="@+id/noneButton"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="@dimen/preference_margin_right"
android:layout_marginRight="@dimen/preference_margin_right"
android:layout_marginTop="@dimen/preference_margin_top"
android:text="@string/key_none" />
<Button
android:id="@+id/skipButton"
style="?android:attr/buttonStyleSmall"
android:layout_below="@id/noneButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"

View File

@@ -1,39 +1,213 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/black"
android:orientation="vertical"
android:baselineAligned="false"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
android:layout_height="fill_parent"
android:layout_marginBottom="5dip"
android:layout_marginLeft="5dip"
android:layout_marginStart="5dip"
android:layout_marginRight="5dip"
android:layout_marginEnd="5dip"
android:layout_marginTop="5dip"
android:background="@color/black"
android:layout_weight="1">
<!-- disk selection header -->
<LinearLayout
android:background="@color/black"
android:orientation="horizontal"
android:baselineAligned="false"
android:id="@+id/disks_selection_header"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
android:layout_height="wrap_content"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- newschool checkbox -->
<CheckBox
android:id="@+id/newschoolDiskSelectionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/disk_selection_newschoool" />
<!-- spacer that works with API 10 ... -->
<TextView
android:id="@+id/header_disks"
style="?android:attr/listSeparatorTextViewStyle"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:text="@string/header_disks" />
android:layout_weight="1" />
<Button
android:id="@+id/ejectButton1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@android:drawable/ic_menu_close_clear_cancel"
android:drawableStart="@android:drawable/ic_menu_close_clear_cancel"
android:id="@+id/cancelButton" />
android:text="@string/header_eject_1" />
<Button
android:id="@+id/ejectButton2"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/header_eject_2" />
<!-- spacer that works with API 10 ... -->
<TextView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />
<Button
android:id="@+id/cancelButton"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableEnd="@android:drawable/ic_menu_close_clear_cancel"
android:drawableRight="@android:drawable/ic_menu_close_clear_cancel" />
</LinearLayout>
<!-- oldschool list view -->
<ListView
android:layout_width="fill_parent"
android:layout_height="0dp"
android:id="@+id/listView_settings"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignLeft="@id/disks_selection_header"
android:layout_alignStart="@id/disks_selection_header"
android:layout_below="@id/disks_selection_header"
android:visibility="invisible"
android:layout_weight="1" />
</LinearLayout>
<!-- newschool file chooser -->
<LinearLayout
android:id="@+id/disk_selection_newschool_chooser"
android:layout_alignLeft="@id/disks_selection_header"
android:layout_alignStart="@id/disks_selection_header"
android:layout_below="@id/disks_selection_header"
android:layout_marginLeft="5dip"
android:layout_marginStart="5dip"
android:layout_marginRight="5dip"
android:layout_marginEnd="5dip"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_marginTop="20dip"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:text="@string/file_chooser"
android:textAppearance="?android:attr/textAppearanceLarge" />
<!-- spacer that works with API 10 ... -->
<TextView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />
<ImageView
app:srcCompat="@android:drawable/ic_menu_save"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<!-- newschool drive A selection -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/a2_newschool_driveA_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/disk_selection_newschool_chooser"
android:layout_alignStart="@id/disk_selection_newschool_chooser"
android:layout_below="@id/disk_selection_newschool_chooser"
android:gravity="center_vertical"
android:minHeight="?attr/listPreferredItemHeight"
android:paddingLeft="0dp"
android:paddingStart="0dp"
android:paddingRight="?android:attr/scrollbarSize"
android:paddingEnd="?android:attr/scrollbarSize">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dip"
android:layout_marginRight="5dip"
android:layout_marginEnd="5dip"
android:layout_marginTop="5dip"
android:layout_weight="1">
<TextView
android:id="@+id/a2_newschool_driveA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:text="@string/diskA"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/a2_newschool_diskA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/a2_newschool_driveA"
android:layout_alignStart="@id/a2_newschool_driveA"
android:layout_below="@id/a2_newschool_driveA"
android:maxLines="4"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
</LinearLayout>
<!-- newschool drive B selection -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/a2_newschool_driveB_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/a2_newschool_driveA_layout"
android:layout_alignStart="@id/a2_newschool_driveA_layout"
android:layout_below="@id/a2_newschool_driveA_layout"
android:gravity="center_vertical"
android:minHeight="?attr/listPreferredItemHeight"
android:paddingLeft="0dp"
android:paddingStart="0dp"
android:paddingRight="?android:attr/scrollbarSize"
android:paddingEnd="?android:attr/scrollbarSize">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dip"
android:layout_marginRight="5dip"
android:layout_marginTop="5dip"
android:layout_weight="1">
<TextView
android:id="@+id/a2_newschool_driveB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:text="@string/diskB"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/a2_newschool_diskB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/a2_newschool_driveB"
android:layout_alignStart="@id/a2_newschool_driveB"
android:layout_below="@id/a2_newschool_driveB"
android:maxLines="4"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/splashScreen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -16,14 +17,14 @@
android:layout_height="fill_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/apple_iie"
app:srcCompat="@drawable/apple_iie"
android:id="@+id/splashView" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView"
android:src="@drawable/ic_launcher"
app:srcCompat="@drawable/ic_launcher"
android:layout_alignTop="@+id/startButton"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
@@ -45,8 +46,8 @@
<Button
android:layout_height="wrap_content"
android:layout_width="0dp"
android:text="@string/emulation_settings"
android:id="@+id/prefsButton"
android:text="@string/reset_preferences"
android:id="@+id/resetButton"
android:layout_marginTop="10dp"
android:layout_below="@+id/startButton"
android:layout_alignRight="@+id/startButton"
@@ -60,7 +61,7 @@
android:text="@string/emulation_disks"
android:id="@+id/disksButton"
android:layout_marginTop="10dp"
android:layout_below="@+id/prefsButton"
android:layout_below="@+id/resetButton"
android:layout_alignRight="@+id/startButton"
android:layout_alignEnd="@+id/startButton"
android:layout_alignLeft="@+id/startButton"

View File

@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#000000</color>
<color name="white">#ffffff</color>
<!-- developer-only options -->
<string name="crasher_summary">Test crash generation</string>
<string name="crasher_title">Crash emulator</string>
<string name="crash_null">NULL-deref</string>
<string name="crash_java_npe">Java NPE</string>
<string name="crash_stackcall_overflow">stack call overflow</string>
<string name="crash_stackbuf_overflow">stack buffer overflow</string>
<!-- main options -->
<string name="about_apple2ix">Über Apple2ix…</string>
<string name="about_apple2ix_summary">Über diese Software</string>
<string name="about_apple2ix_licenses">Licenses…</string>
<string name="about_apple2ix_licenses_summary">Apple2ix software licenses</string>
<string name="app_name">Apple2ix</string>
<string name="audio_configure">Audio-Konfiguration…</string>
<string name="audio_configure_summary">Lautstärke, Mockingboard, etc</string>
<string name="audio_latency">Audio Latency</string>
<string name="audio_latency_summary">Audio Latency in Sek.</string>
<string name="cancel">Abbrechen</string>
<string name="color_bw">Schwarz/Weiss</string>
<string name="color_color">Farbe</string>
<string name="color_interpolated">Interpolierte Farben</string>
<string name="crasher">Abstürze</string>
<string name="crasher_check_title">Prüfe auf Absturz-Berichte</string>
<string name="crasher_check_summary">Prüfe auf Absturz-Berichte zum Versand an den Entwickler</string>
<string name="crasher_send">Sende Absturz-Bericht?</string>
<string name="crasher_send_message">Entschuldigung. es gab ein Problemm. Möchten Sie einen Absturz-Bericht an den Entwickler senden?</string>
<string name="diskA">Laufwerk 1</string>
<string name="diskB">Laufwerk 2</string>
<string name="disk_eject">Auswerfen</string>
<string name="disk_insert_toast_cannot">Cannot insert (not a disk image or state file)</string>
<string name="disk_read_only">Schreibgeschützt</string>
<string name="disk_read_write">Lesen/Schreiben</string>
<string name="disk_selection_newschoool">System chooser</string>
<string name="disk_show_operation">Zeige Disk ][ Aktivität</string>
<string name="disk_show_operation_summary">Zeigt wenn die Laufwerke lesen oder schreiben</string>
<string name="emulation_continue">Fortsetzen…</string>
<string name="emulation_disks">Lade Disk-Image…</string>
<string name="file_chooser">Choose disk image or state file…</string>
<string name="header_disks">Diskette einlegen:</string>
<string name="header_eject_1">Auswerfen 1</string>
<string name="header_eject_2">Auswerfen 2</string>
<string name="input_current">Aktuelles Touch Device</string>
<string name="input_current_summary">Wähle ein aktuelles Touch Device</string>
<string name="joystick">Joystick</string>
<string name="joystick_axis_sensitivity_summary">Empfindlichkeiteinstellung der Joystickachsen (verlangsamen or beschleunigen)</string>
<string name="joystick_button_button1">Button 1 (Offener Apfel)</string>
<string name="joystick_button_button2">Button 2 (Geschlossener Apfel)</string>
<string name="joystick_button_button_both">Beide</string>
<string name="joystick_button_button_none">Keine</string>
<string name="joystick_button_tap_button">Drücke den Feuerknopf</string>
<string name="joystick_button_tap_button_summary">Ausgewählter Feuerknopf</string>
<string name="joystick_button_swipe_up_button">Aufwärts wischen zum feuern</string>
<string name="joystick_button_swipe_up_button_summary">Feuerknopf zum feuern beim aufwärts wischen</string>
<string name="joystick_button_swipe_down_button">Abwärts wischen zum feuern</string>
<string name="joystick_button_swipe_down_button_summary">Feuerknopf zum feuern beim abwärts wischen</string>
<string name="joystick_button_threshold_summary">Joystick/Keypad Knopf Schaltschwellwert in pts</string>
<string name="joystick_calibrate">Kalibrieren…</string>
<string name="joystick_calibrate_summary">Konfiguriere und teste die aktuellen Einstellungen</string>
<string name="joystick_configure">Joystickkonfiguration…</string>
<string name="joystick_configure_summary">Achsen Touch, Knöpfe, etc</string>
<string name="joystick_axisleft">Joystick/Keypad Achse links</string>
<string name="joystick_axisleft_summary">Joystick/Keypad Achse links (Knöpfe rechts)</string>
<string name="joystick_visible">Sichtbarkeit Joystick/Keypad</string>
<string name="joystick_visible_summary">Zeige die Kontrollen als Overlay wenn aktiviert</string>
<string name="key_closed_apple">[GeschlossenerApfel]</string>
<string name="key_ctrl">[Strg]</string>
<string name="key_down">&#8595;</string>
<string name="key_esc">[ESC]</string>
<string name="key_left">&#8592;</string>
<string name="key_none">[Keine]</string>
<string name="key_open_apple">[OffenerApfel]</string>
<string name="key_ret">[Return]</string>
<string name="key_right">&#8594;</string>
<string name="key_space">[Space]</string>
<string name="key_up">&#8593;</string>
<string name="keyboard">Tastatur</string>
<string name="keyboard_choose_alt">Wähle alternative Tastatur…</string>
<string name="keyboard_choose_alt_summary">Wähle alternativ konfiguriertes Layout</string>
<string name="keyboard_click_enabled">Tastenton einschalten</string>
<string name="keyboard_click_enabled_summary">Aktiviert Tastenton wenn verfügbar</string>
<string name="keyboard_configure">Tastatur-Konfiguration…</string>
<string name="keyboard_configure_summary">Transparenz, Kleinbuchstaben, eigene Tasten</string>
<string name="keyboard_glyph_scale">Tastatur-Glyphenskalierung 2x</string>
<string name="keyboard_glyph_scale_summary">(Lässt die Tastaur weniger pixelig erscheinen auf grossen Bildschirmen)</string>
<string name="keyboard_lowercase_enabled">Kleinbuchstaben aktivieren</string>
<string name="keyboard_lowercase_enabled_summary">Aktiviren der Tasten für Kleinbuchstaben</string>
<string name="keyboard_visibility_active">Sichtbarkeit wenn aktiviert</string>
<string name="keyboard_visibility_active_summary">Sichtbarkeit des Keyboard und Touch Menüs wenn aktiviert</string>
<string name="keyboard_visibility_inactive">Sichtbarkeit wenn deaktiviert</string>
<string name="keyboard_visibility_inactive_summary">Sichtbarkeit des Keyboard und Touch Menüs wenn deaktiviert</string>
<string name="keypad">Keypad Joystick</string>
<string name="keypad_choose">Auswahl der Keypad Tasten…</string>
<string name="keypad_choose_summary">Auswahl der Achsen und Knopf Tasten</string>
<string name="keypad_choose_title">Achse &amp; Knöpfe</string>
<string name="keypad_choose_current">Wähle XXX Taste: </string>
<string name="keypad_configure">Konfiguration Joystick Keypad…</string>
<string name="keypad_configure_summary">@string/joystick_configure_summary</string>
<string name="keypad_key_axis_c">Mitte</string>
<string name="keypad_key_axis_dn">Unten</string>
<string name="keypad_key_axis_dl">Unten und Links</string>
<string name="keypad_key_axis_dr">Unten und Rechts</string>
<string name="keypad_key_axis_l">Links</string>
<string name="keypad_key_axis_r">Rechts</string>
<string name="keypad_key_axis_ul">Oben und Links</string>
<string name="keypad_key_axis_up">Oben</string>
<string name="keypad_key_axis_ur">Oben und Rechts</string>
<string name="keypad_key_button_tap">antippen</string>
<string name="keypad_key_button_swipeup">Nach oben wischen</string>
<string name="keypad_key_button_swipedown">Nach unten wischen</string>
<string name="keypad_preset_seafox">Seafox Tasten…</string>
<string name="keypad_preset_custom">Auswahl einer Anpassung…</string>
<string name="keypad_preset_arrows_space">(↑ ← → ↓), tippe auf die Leerstaste</string>
<string name="keypad_preset_az_left_right_space">(A Z ← →), tippe auf die Leerstaste</string>
<string name="keypad_preset_ijkm_space">(I J K M), tippe auf die Leertaste</string>
<string name="keypad_preset_left_right_space">(← →), tippe auf die Leertaste</string>
<string name="keypad_preset_wadx_space">(W A D X), tippe auf die Leertaste</string>
<string name="menu_disks">Lade Disk-Image…</string>
<string name="menu_disks_summary">Einlegen eines Disk ][ Image</string>
<string name="menu_settings">Emulator Einstellungen…</string>
<string name="menu_settings_summary">Allgemeine Einstellungen, Joystick, Tastatur</string>
<string name="mockingboard_disabled_title">Mockingboard deaktiviert</string>
<string name="mockingboard_disabled_mesg">Mockingboard konnte nicht aktiviert werden</string>
<string name="mockingboard_enable">Aktiviere Mockingboard</string>
<string name="mockingboard_enable_summary">Revision C in Slot 4/5 (evtl. wird ein Restart benötigt) (may consume extra battery)</string>
<string name="mockingboard_volume">Mockingboard Lautstärke</string>
<string name="mockingboard_volume_summary">Einstellen der Mockingboard Lautstärke</string>
<string name="no">Nein</string>
<string name="ok">OK</string>
<string name="preferences_reset_title">Einstellungen zurücksetzen</string>
<string name="preferences_reset_summary">Einstellungen auf Standard zurücksetzen und Emulator beenden</string>
<string name="preferences_reset_really">Wollen Sie wirklich resetten und beenden?</string>
<string name="preferences_reset_warning">Sie werden alle Ihre Einstellungen verlieren</string>
<string name="quit">Beenden</string>
<string name="quit_reboot">Neustart oder beenden des Emulators…</string>
<string name="quit_reboot_choice">Neustart oder beenden?&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#13;&#10;</string>
<string name="reboot">Neustart</string>
<string name="restore">Schnelle Wiederherstellung</string>
<string name="save">Schnelle Speicherung</string>
<string name="saverestore">Speichern &amp; wiederherstellen…</string>
<string name="saverestore_choice">Aktuellen Status sichern oder vorherigen wiederherstellen?</string>
<string name="saverestore_summary">Schnellspeicherung und Wiederherstellung</string>
<string name="skip">Überspringen&#8594;</string>
<string name="speaker_volume">Lautsprecherlautstärke</string>
<string name="speaker_volume_summary">Lautsprecherlautstärke einstellen</string>
<string name="settings">Apple2ix Emulator Einstellungen</string>
<string name="settings_advanced">Erweiterte Einstellungen</string>
<string name="settings_advanced_summary">Warnung: diese Einstellungen verschlechtern die Performance</string>
<string name="settings_advanced_joystick">Erweiterte Joystick/Keypad Einstellungen</string>
<string name="settings_advanced_joystick_summary">Erweiterte Einstellungen für das Performance-Tuning</string>
<string name="touch_menu_enable">Aktiviere Touch Menüs</string>
<string name="touch_menu_enable_summary">Aktiviere Softmenü Knöpfe in den oberen Ecken des Bildschirms</string>
<string name="video_configure">Video-Konfiguration…</string>
<string name="video_configure_summary">Farbeinstellungen</string>
<string name="color_configure">Video display mode</string>
<string name="color_configure_summary">Video display emulation mode</string>
<string name="joystick_azimuth_visible">Show joystick/keypad heading</string>
<string name="joystick_azimuth_visible_summary">Shows current axis direction and magnitude</string>
<string name="mode_landscape">Landscape</string>
<string name="mode_landscape_summary">Enable landscape or portrait mode</string>
<string name="portrait_calibrate">Calibrate portrait mode</string>
<string name="portrait_calibrate_framebuffer">Display position</string>
<string name="portrait_calibrate_keyboard_height">Keyboard height</string>
<string name="portrait_calibrate_keyboard_position">Keyboard position</string>
<string name="portrait_calibrate_summary">Adjust keyboard size, framebuffer position, etc</string>
<string name="crash_sigabrt">SIGABRT</string>
<string name="crash_sigfpe">SIGFPE</string>
<string name="reset">Reset</string>
<string name="reset_preferences">Reset settings</string>
<string name="reset_self_test">Self Test</string>
<string name="reset_soft">Soft</string>
<string name="touch_device_color">Configure touch device color</string>
<string name="touch_device_color_summary">Configure color of HUD elements</string>
<string name="state_not_restored">Error restoring state…</string>
<string name="storage">/storage/</string>
<string name="touch_device_color_configure">Configure HUD color…</string>
<string name="color_red_on_black">Red on black</string>
<string name="color_green_on_black">Green on black</string>
<string name="color_blue_on_black">Blue on black</string>
<string name="color_white_on_black">White on black</string>
<string name="color_monitor">Color Monitor</string>
<string name="color_tv_mono">Monochrome TV</string>
<string name="color_tv">Color TV</string>
<string name="show_half_scanlines">Show half scanlines</string>
<string name="mono_configure">Monocolor mode</string>
<string name="mono_configure_summary">Configure monochrome color</string>
<string name="color_green">Green screen</string>
<string name="color_mono">Monochrome</string>
<string name="disk_fast_operation">Disk fast loading (EXPERIMENTAL)</string>
<string name="disk_fast_operation_summary">Quickly load disk images (may negatively affect device battery life)</string>
<string name="show_half_scanlines_summary">Renders pixel vertical divisions</string>
<string name="release_notes">Release notes</string>
<string name="release_notes_summary">View notes for this release</string>
<string name="keyboard_duotouch_enabled">Enable dual-touch</string>
<string name="keyboard_duotouch_enabled_summary">Support two-thumb input</string>
<string name="keypad_preset_qaz_left_right_space">(A Z ← →), Leertaste, Q</string>
<string name="keypad_tapdelay_summary">Keypad touch down delay in secs</string>
<string name="keypad_autorepeat_fast_summary">Allows immediate auto-repeat (avoids original 534-801 millis delay)</string>
<string name="keypad_autorepeat_fast">Keypad Immediate Autorepeat</string>
<string name="string_frames">Frames</string>
<string name="string_millis">Millis</string>
<string name="joystick_button_tapdelay_summary">Joystick touch down delay in video frames</string>
<string name="keypad_preset_robotron">Robotron Tasten…</string>
<string name="keypad_preset_loderunner">Lode Runner Tasten…</string>
<string name="keypad_preset_loderunner_toast">Start game with Ctrl-K to activate keyboard</string>
<string name="joystick_button_swipe_left_button">Swipe left fire</string>
<string name="joystick_button_swipe_right_button">Swipe right fire</string>
<string name="joystick_button_swipe_left_button_summary">Button to fire on swipe left</string>
<string name="joystick_button_swipe_right_button_summary">Button to fire on swipe right</string>
<string name="key_del">[DEL]</string>
<string name="key_tab">[TAB]</string>
<string name="preferences_email_logs">Email logs</string>
<string name="preferences_email_logs_summary">Email logs to developer…</string>
</resources>

View File

@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#000000</color>
<color name="white">#ffffff</color>
<!-- developer-only options -->
<string name="crasher_summary">Test crash generation</string>
<string name="crasher_title">Crash emulator</string>
<string name="crash_null">NULL-deref</string>
<string name="crash_java_npe">Java NPE</string>
<string name="crash_stackcall_overflow">stack call overflow</string>
<string name="crash_stackbuf_overflow">stack buffer overflow</string>
<!-- main options -->
<string name="about_apple2ix">Acerca de Apple2ix…</string>
<string name="about_apple2ix_summary">Acerca de este software</string>
<string name="about_apple2ix_licenses">Licenses…</string>
<string name="about_apple2ix_licenses_summary">Apple2ix software licenses</string>
<string name="app_name">Apple2ix</string>
<string name="audio_configure">Configurar audio…</string>
<string name="audio_configure_summary">Ajustar el volumen del altavoz, "Mockingboard", etc</string>
<string name="audio_latency">La latencia de audio</string>
<string name="audio_latency_summary">La latencia de audio en segundos</string>
<string name="cancel">Cancelar</string>
<string name="color_bw">Blanco/negro</string>
<string name="color_color">Color</string>
<string name="color_interpolated">Color interpolado</string>
<string name="crasher">Fallos del software</string>
<string name="crasher_check_title">Fallos del software</string>
<string name="crasher_check_summary">Verificar la presencia de fallos del software</string>
<string name="crasher_send">¿Enviar informe de error?</string>
<string name="crasher_send_message">Lo sentimos, ha habido un problema. ¿Desea enviar informe de error para el programador de software?</string>
<string name="diskA">Disquetera 1</string>
<string name="diskB">Disquetera 2</string>
<string name="disk_eject">Eyectar</string>
<string name="disk_insert_toast_cannot">Cannot insert (not a disk image or state file)</string>
<string name="disk_read_only">Sólo leer</string>
<string name="disk_read_write">Leer y escribir</string>
<string name="disk_selection_newschoool">System chooser</string>
<string name="disk_show_operation">Mostrar las operaciones de "Disk ]["</string>
<string name="disk_show_operation_summary">Shows when disk drives are reading or writing</string>
<string name="emulation_continue">Continuar…</string>
<string name="emulation_disks">Insertar imagen de disquete…</string>
<string name="file_chooser">Choose disk image or state file…</string>
<string name="header_disks">Insertar imagen de disquete:</string>
<string name="header_eject_1">Eyectar 1</string>
<string name="header_eject_2">Eyectar 2</string>
<string name="input_current">Unidad de entrada actual</string>
<string name="input_current_summary">Elija unidad de entrada</string>
<string name="joystick">"Joystick"</string>
<string name="joystick_axis_sensitivity_summary">Afinar la sensibilidad del eje del joystick (desacelerar o acelerar)</string>
<string name="joystick_button_button1">Botón 1</string>
<string name="joystick_button_button2">Botón 2</string>
<string name="joystick_button_button_both">Los dos</string>
<string name="joystick_button_button_none">Ninguno</string>
<string name="joystick_button_tap_button">Toque para disparar</string>
<string name="joystick_button_tap_button_summary">Botón para disparar sobre toque abajo</string>
<string name="joystick_button_swipe_up_button">Pase hacia arriba</string>
<string name="joystick_button_swipe_up_button_summary">Botón para disparar sobre pase el dedo hacia arriba</string>
<string name="joystick_button_swipe_down_button">Pase hacia abajo</string>
<string name="joystick_button_swipe_down_button_summary">Botón para disparar sobre pase el dedo hacia abajo</string>
<string name="joystick_button_threshold_summary">Umbral del joystick en puntos</string>
<string name="joystick_calibrate">Calibrar…</string>
<string name="joystick_calibrate_summary">Calibrar el joystick</string>
<string name="joystick_configure">Configurar el joystick…</string>
<string name="joystick_configure_summary">Eje táctil, los botónes, etc</string>
<string name="joystick_axisleft">Eje del joystick a la izquierda</string>
<string name="joystick_axisleft_summary">Eje del joystick a la izquierda (los botónes de la derecha)</string>
<string name="joystick_visible">Visibilidad del joystick</string>
<string name="joystick_visible_summary">Mostrar controles cuando se utiliza</string>
<string name="key_closed_apple">[Botón 2]</string>
<string name="key_ctrl">[Ctrl]</string>
<string name="key_down">&#8595;</string>
<string name="key_esc">[ESC]</string>
<string name="key_left">&#8592;</string>
<string name="key_none">[Ninguno]</string>
<string name="key_open_apple">[Botón 1]</string>
<string name="key_ret">[Retorno]</string>
<string name="key_right">&#8594;</string>
<string name="key_space">[Espaciadora]</string>
<string name="key_up">&#8593;</string>
<string name="keyboard">Teclado</string>
<string name="keyboard_choose_alt">Elija teclado alternativo…</string>
<string name="keyboard_choose_alt_summary">Elija el diseño de teclado alternativo</string>
<string name="keyboard_click_enabled">Habilitar la tecla de sonido de clic</string>
<string name="keyboard_click_enabled_summary">Haga clic en sonido de clave permite si está disponible</string>
<string name="keyboard_configure">Configurar el teclado…</string>
<string name="keyboard_configure_summary">Transparencia, minúsculas, teclado personalizado</string>
<string name="keyboard_lowercase_enabled">Habilitar minúsculas</string>
<string name="keyboard_lowercase_enabled_summary">Utilice las teclas minúsculas</string>
<string name="keyboard_visibility_active">Visibilidad cuando está activo</string>
<string name="keyboard_visibility_active_summary">Visibilidad del teclado y menú cuando está activo</string>
<string name="keyboard_visibility_inactive">Visibilidad cuando está inactivo</string>
<string name="keyboard_visibility_inactive_summary">Visibilidad del teclado y menú cuando está inactivo</string>
<string name="keypad">Joystick como teclado numérico</string>
<string name="keypad_choose">Teclas del teclado numérico…</string>
<string name="keypad_choose_summary">Elegir las teclas del joystick del teclado numérico</string>
<string name="keypad_choose_title">Ejes y botones</string>
<string name="keypad_choose_current">Elija [XXX] tecla del teclado: </string>
<string name="keypad_configure">Configurar el joystick del teclado numérico…</string>
<string name="keypad_configure_summary">@string/joystick_configure_summary</string>
<string name="keypad_key_axis_c">Centro</string>
<string name="keypad_key_axis_dn">Abajo</string>
<string name="keypad_key_axis_dl">Abajo y a la izquierda</string>
<string name="keypad_key_axis_dr">Abajo y a la derecha</string>
<string name="keypad_key_axis_l">Izquierda</string>
<string name="keypad_key_axis_r">Derecha</string>
<string name="keypad_key_axis_ul">Arriba y a la izquierda</string>
<string name="keypad_key_axis_up">Arriba</string>
<string name="keypad_key_axis_ur">Arriba y a la derecha</string>
<string name="keypad_key_button_tap">Toque</string>
<string name="keypad_key_button_swipeup">Desliza el dedo hacia arriba</string>
<string name="keypad_key_button_swipedown"> Desliza el dedo hacia abajo</string>
<string name="keypad_preset_seafox">Tecla para Seafox…</string>
<string name="keypad_preset_custom">Elija personalizado…</string>
<string name="keypad_preset_arrows_space">(↑ ← → ↓), pulse barra espaciadora</string>
<string name="keypad_preset_az_left_right_space">(A Z ← →), pulse barra espaciadora</string>
<string name="keypad_preset_ijkm_space">(I J K M), pulse barra espaciadora</string>
<string name="keypad_preset_left_right_space">(← →), pulse barra espaciadora</string>
<string name="keypad_preset_wadx_space">(W A D X), pulse barra espaciadora</string>
<string name="menu_disks">Insertar imagen de disco…</string>
<string name="menu_disks_summary">Insertar imagen de "Disk ]["</string>
<string name="menu_settings">Configuración del emulador…</string>
<string name="menu_settings_summary">Configuración general, joystick, teclado</string>
<string name="mockingboard_disabled_title">Mockingboard desactivado</string>
<string name="mockingboard_disabled_mesg">Mockingboard no pudo ser habilitado</string>
<string name="mockingboard_enable">Activar Mockingboard</string>
<string name="mockingboard_enable_summary">Revisión C en Slot 4/5 (puede requerir reinicio) (may consume extra battery)</string>
<string name="mockingboard_volume">Volumen de Mockingboard</string>
<string name="mockingboard_volume_summary">Adjustar el volumen del Mockingboard</string>
<string name="no">No</string>
<string name="ok"></string>
<string name="preferences_reset_title">Restablecer preferencias</string>
<string name="preferences_reset_summary">Restablecer preferencias y salir</string>
<string name="preferences_reset_really">¿Restablecer realmente y salir?</string>
<string name="preferences_reset_warning">Usted perderá su configuración</string>
<string name="quit">Salir</string>
<string name="quit_reboot">Reiniciar o salir el emulador…</string>
<string name="quit_reboot_choice">¿Reiniciar o salir?&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#13;&#10;</string>
<string name="reboot">Reiniciar</string>
<string name="restore">Restauración rápida</string>
<string name="save">Guardar rápido</string>
<string name="saverestore">Guardar y restaurar…</string>
<string name="saverestore_choice">¿Guardar el estado actual o anterior de restauración?</string>
<string name="saverestore_summary">Guardar y restaurar rápida</string>
<string name="skip">Saltar&#8594;</string>
<string name="speaker_volume">El volumen del altavoz</string>
<string name="speaker_volume_summary">Ajustar el volumen del altavoz</string>
<string name="settings">Configuración Apple2ix</string>
<string name="settings_advanced">Configuración avanzada</string>
<string name="settings_advanced_summary">Advertencia: estos valores puede degradar el rendimiento</string>
<string name="settings_advanced_joystick">Configuración de teclado y joystick avanzados</string>
<string name="settings_advanced_joystick_summary">Configuración avanzada y optimización del rendimiento</string>
<string name="touch_menu_enable">Activar menús táctiles</string>
<string name="touch_menu_enable_summary">Los botones del menú en la parte superior de la pantalla</string>
<string name="video_configure">Configurar el video…</string>
<string name="video_configure_summary">Ajustes de color</string>
<string name="color_configure">Video display mode</string>
<string name="color_configure_summary">Video display emulation mode</string>
<string name="joystick_azimuth_visible">Show joystick/keypad heading</string>
<string name="joystick_azimuth_visible_summary">Shows current axis direction and magnitude</string>
<string name="mode_landscape">Landscape</string>
<string name="mode_landscape_summary">Enable landscape or portrait mode</string>
<string name="portrait_calibrate">Calibrate portrait mode</string>
<string name="portrait_calibrate_framebuffer">Display position</string>
<string name="portrait_calibrate_keyboard_height">Keyboard height</string>
<string name="portrait_calibrate_keyboard_position">Keyboard position</string>
<string name="portrait_calibrate_summary">Adjust keyboard size, framebuffer position, etc</string>
<string name="keyboard_glyph_scale">Keyboard glyphs scaled 2x</string>
<string name="keyboard_glyph_scale_summary">(Makes keyboard appear less pixelated on large screens)</string>
<string name="crash_sigabrt">SIGABRT</string>
<string name="crash_sigfpe">SIGFPE</string>
<string name="reset">Reset</string>
<string name="reset_preferences">Reset settings</string>
<string name="reset_self_test">Self Test</string>
<string name="reset_soft">Soft</string>
<string name="touch_device_color">Configure touch device color</string>
<string name="touch_device_color_summary">Configure color of HUD elements</string>
<string name="state_not_restored">Error restoring state…</string>
<string name="storage">/storage/</string>
<string name="touch_device_color_configure">Configure HUD color…</string>
<string name="color_red_on_black">Red on black</string>
<string name="color_green_on_black">Green on black</string>
<string name="color_blue_on_black">Blue on black</string>
<string name="color_white_on_black">White on black</string>
<string name="color_monitor">Color Monitor</string>
<string name="color_tv_mono">Monochrome TV</string>
<string name="color_tv">Color TV</string>
<string name="show_half_scanlines">Show half scanlines</string>
<string name="mono_configure">Monocolor mode</string>
<string name="mono_configure_summary">Configure monochrome color</string>
<string name="color_green">Green screen</string>
<string name="color_mono">Monochrome</string>
<string name="disk_fast_operation">Disk fast loading (EXPERIMENTAL)</string>
<string name="disk_fast_operation_summary">Quickly load disk images (may negatively affect device battery life)</string>
<string name="show_half_scanlines_summary">Renders pixel vertical divisions</string>
<string name="release_notes">Release notes</string>
<string name="release_notes_summary">View notes for this release</string>
<string name="keyboard_duotouch_enabled">Enable dual-touch</string>
<string name="keyboard_duotouch_enabled_summary">Support two-thumb input</string>
<string name="keypad_preset_qaz_left_right_space">(A Z ← →), barra espaciadora, Q</string>
<string name="keypad_tapdelay_summary">Keypad touch down delay in secs</string>
<string name="keypad_autorepeat_fast_summary">Allows immediate auto-repeat (avoids original 534-801 millis delay)</string>
<string name="keypad_autorepeat_fast">Keypad Immediate Autorepeat</string>
<string name="string_frames">Frames</string>
<string name="string_millis">Millis</string>
<string name="joystick_button_tapdelay_summary">Joystick touch down delay in video frames</string>
<string name="keypad_preset_robotron">Tecla para Robotron…</string>
<string name="keypad_preset_loderunner">Lode Runner keys…</string>
<string name="keypad_preset_loderunner_toast">Start game with Ctrl-K to activate keyboard</string>
<string name="joystick_button_swipe_left_button">Swipe left fire</string>
<string name="joystick_button_swipe_right_button">Swipe right fire</string>
<string name="joystick_button_swipe_left_button_summary">Button to fire on swipe left</string>
<string name="joystick_button_swipe_right_button_summary">Button to fire on swipe right</string>
<string name="key_del">[DEL]</string>
<string name="key_tab">[TAB]</string>
<string name="preferences_email_logs">Email logs</string>
<string name="preferences_email_logs_summary">Email logs to developer…</string>
</resources>

View File

@@ -15,6 +15,8 @@
<!-- main options -->
<string name="about_apple2ix">A propos d\'Apple2ix…</string>
<string name="about_apple2ix_summary">A propos de ce logiciel</string>
<string name="about_apple2ix_licenses">Licenses…</string>
<string name="about_apple2ix_licenses_summary">Apple2ix software licenses</string>
<string name="app_name">Apple2ix</string>
<string name="audio_configure">Configuration audio…</string>
<string name="audio_configure_summary">Volume du haut-parleur, Mockingboard, etc</string>
@@ -32,16 +34,18 @@
<string name="diskA">Lecteur 1</string>
<string name="diskB">Lecteur 2</string>
<string name="disk_eject">Ejecter</string>
<string name="disk_insert_toast">Insérer la disquette dans le drive en lecture seulement</string>
<string name="disk_insert_could_not_read">Désolé, impossible de lire l\'image disque!</string>
<string name="disk_insert_toast_cannot">Cannot insert (not a disk image or state file)</string>
<string name="disk_selection_newschoool">System chooser</string>
<string name="disk_read_only">Lecture seulement</string>
<string name="disk_read_write">Lecture/Ecriture</string>
<string name="disk_show_operation">Afficher les opérations (disque) ][</string>
<string name="disk_show_operation_summary">Indique si les disques sont en lecture ou en écriture</string>
<string name="emulation_continue">Continuer…</string>
<string name="emulation_settings">Configurations…</string>
<string name="emulation_disks">Chargement de l\'image disque…</string>
<string name="file_chooser">Choose disk image or state file…</string>
<string name="header_disks">Insérer la disquettte :</string>
<string name="header_eject_1">Ejecter 1</string>
<string name="header_eject_2">Ejecter 2</string>
<string name="input_current">Tactile</string>
<string name="input_current_summary">Choisir l\'appareil courant</string>
<string name="joystick">Joystick</string>
@@ -52,7 +56,6 @@
<string name="joystick_button_button_none">Rien</string>
<string name="joystick_button_tap_button">Sélectionner l\'action</string>
<string name="joystick_button_tap_button_summary">Bouton à activer lors d\'une pression vers le bas</string>
<string name="joystick_button_tapdelay_summary">Délai de pression du bouton Joystick en secondes</string>
<string name="joystick_button_swipe_up_button">Lancement du swipe up</string>
<string name="joystick_button_swipe_up_button_summary">Bouton à lancer sur swipe up</string>
<string name="joystick_button_swipe_down_button">Lancement du swipe down</string>
@@ -87,12 +90,10 @@
<string name="keyboard_lowercase_enabled">Permettre les minuscules</string>
<string name="keyboard_lowercase_enabled_summary">Permettre les touches en minuscules</string>
<string name="keyboard_visibility_active">Visibilité quand actif</string>
<string name="keyboard_visibility_active_summary">Voir le clavier quand actif</string>
<string name="keyboard_visibility_active_summary">Clavier et menu tactile visible quand actif</string>
<string name="keyboard_visibility_inactive">Visibilité quand inactif</string>
<string name="keyboard_visibility_inactive_summary">Clavier visible quand inactif</string>
<string name="keyboard_visibility_inactive_summary">Clavier et menu tactile visible quand inactif</string>
<string name="keypad">Keypad Joystick</string>
<string name="keypad_calibrate">@string/joystick_calibrate</string>
<string name="keypad_calibrate_summary">@string/joystick_calibrate</string>
<string name="keypad_choose">Sélection des touches du keypad…</string>
<string name="keypad_choose_summary">Sélection des touches pour les axes et boutons</string>
<string name="keypad_choose_title">Axis &amp; boutons</string>
@@ -111,14 +112,13 @@
<string name="keypad_key_button_tap">Presser</string>
<string name="keypad_key_button_swipeup">Slider vers le haut</string>
<string name="keypad_key_button_swipedown">Slider vers le bas</string>
<string name="keypad_preset_crazy_seafox">Touches Seafox ;-)</string>
<string name="keypad_preset_seafox">Touches Seafox…</string>
<string name="keypad_preset_custom">Choisir customisation…</string>
<string name="keypad_preset_arrows_space">&#8593;,&#8592;,&#8594;,&#8595;, pressez barre d\'espace</string>
<string name="keypad_preset_az_left_right_space">A,Z,&#8592;,&#8594;, pressez barre d\'espace</string>
<string name="keypad_preset_ijkm_space">I,J,K,M, pressez barre d\'espace</string>
<string name="keypad_preset_left_right_space">&#8592;,&#8594;, pressez barre d\'espace</string>
<string name="keypad_preset_wadx_space">W,A,D,X, pressez barre d\'espace</string>
<string name="keypad_repeat_summary">Répétition des touches en secs</string>
<string name="keypad_preset_arrows_space">(↑ ← → ↓), pressez barre d\'espace</string>
<string name="keypad_preset_az_left_right_space">(A Z ← →), pressez barre d\'espace</string>
<string name="keypad_preset_ijkm_space">(I J K M), pressez barre d\'espace</string>
<string name="keypad_preset_left_right_space">(← →), pressez barre d\'espace</string>
<string name="keypad_preset_wadx_space">(W A D X), pressez barre d\'espace</string>
<string name="menu_disks">Chargement de l\'image disque…</string>
<string name="menu_disks_summary">Insérer un fichier image (disque) ][</string>
<string name="menu_settings">Paramètres de l\'émulateur…</string>
@@ -126,7 +126,7 @@
<string name="mockingboard_disabled_title">Mockingboard désactivé</string>
<string name="mockingboard_disabled_mesg">Le Mockingboard ne peut être activé</string>
<string name="mockingboard_enable">Activer le Mockingboard</string>
<string name="mockingboard_enable_summary">Révision C dans Slot 4/5 (redémarrage possible)</string>
<string name="mockingboard_enable_summary">Révision C dans Slot 4/5 (redémarrage possible) (may consume extra battery)</string>
<string name="mockingboard_volume">Volume du Mockingboard</string>
<string name="mockingboard_volume_summary">Placer le volume du Mockingboard</string>
<string name="no">Non</string>
@@ -137,7 +137,7 @@
<string name="preferences_reset_warning">Vous perdrez toutes vos options de configuration</string>
<string name="quit">Quitter</string>
<string name="quit_reboot">Rebooter ou quitter l\'émulateur…</string>
<string name="quit_reboot_choice">Rebooter ou quitter?</string>
<string name="quit_reboot_choice">Rebooter ou quitter?&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#13;&#10;</string>
<string name="reboot">Rebooter</string>
<string name="restore">Restauration rapide</string>
<string name="save">Sauvegarde rapide</string>
@@ -154,9 +154,68 @@
<string name="settings_advanced_joystick_summary">Configuration avancée et tuning de performance</string>
<string name="touch_menu_enable">Activation des menus tactiles</string>
<string name="touch_menu_enable_summary">Activation soft des bouton du menu dans les coins en haut de l\'écran</string>
<string name="touch_menu_visibility">Visibilité du menu tactile</string>
<string name="touch_menu_visibility_summary">Menu tactile visible quand inactif</string>
<string name="video_configure">Configuration de la vidéo…</string>
<string name="video_configure_summary">Configuration des couleurs</string>
<string name="color_configure">Video display mode</string>
<string name="color_configure_summary">Video display emulation mode</string>
<string name="joystick_azimuth_visible">Show joystick/keypad heading</string>
<string name="joystick_azimuth_visible_summary">Shows current axis direction and magnitude</string>
<string name="mode_landscape">Landscape</string>
<string name="mode_landscape_summary">Enable landscape or portrait mode</string>
<string name="portrait_calibrate">Calibrate portrait mode</string>
<string name="portrait_calibrate_framebuffer">Display position</string>
<string name="portrait_calibrate_keyboard_height">Keyboard height</string>
<string name="portrait_calibrate_keyboard_position">Keyboard position</string>
<string name="portrait_calibrate_summary">Adjust keyboard size, framebuffer position, etc</string>
<string name="keyboard_glyph_scale">Keyboard glyphs scaled 2x</string>
<string name="keyboard_glyph_scale_summary">(Makes keyboard appear less pixelated on large screens)</string>
<string name="crash_sigabrt">SIGABRT</string>
<string name="crash_sigfpe">SIGFPE</string>
<string name="reset">Reset</string>
<string name="reset_self_test">Self Test</string>
<string name="reset_preferences">Reset settings</string>
<string name="reset_soft">Soft</string>
<string name="touch_device_color">Configure touch device color</string>
<string name="touch_device_color_summary">Configure color of HUD elements</string>
<string name="state_not_restored">Error restoring state…</string>
<string name="storage">/storage/</string>
<string name="touch_device_color_configure">Configure HUD color…</string>
<string name="color_red_on_black">Red on black</string>
<string name="color_green_on_black">Green on black</string>
<string name="color_blue_on_black">Blue on black</string>
<string name="color_white_on_black">White on black</string>
<string name="color_monitor">Color Monitor</string>
<string name="color_tv_mono">Monochrome TV</string>
<string name="color_tv">Color TV</string>
<string name="show_half_scanlines">Show half scanlines</string>
<string name="mono_configure">Monocolor mode</string>
<string name="mono_configure_summary">Configure monochrome color</string>
<string name="color_green">Green screen</string>
<string name="color_mono">Monochrome</string>
<string name="disk_fast_operation">Disk fast loading (EXPERIMENTAL)</string>
<string name="disk_fast_operation_summary">Quickly load disk images (may negatively affect device battery life)</string>
<string name="show_half_scanlines_summary">Renders pixel vertical divisions</string>
<string name="release_notes">Release notes</string>
<string name="release_notes_summary">View notes for this release</string>
<string name="keyboard_duotouch_enabled">Enable dual-touch</string>
<string name="keyboard_duotouch_enabled_summary">Support two-thumb input</string>
<string name="keypad_preset_qaz_left_right_space">(A Z ← →) barre d\'espace, Q</string>
<string name="keypad_tapdelay_summary">Keypad touch down delay in secs</string>
<string name="keypad_autorepeat_fast_summary">Allows immediate auto-repeat (avoids original 534-801 millis delay)</string>
<string name="keypad_autorepeat_fast">Keypad Immediate Autorepeat</string>
<string name="string_frames">Frames</string>
<string name="string_millis">Millis</string>
<string name="joystick_button_tapdelay_summary">Joystick touch down delay in video frames</string>
<string name="keypad_preset_robotron">Touches Robotron…</string>
<string name="keypad_preset_loderunner">Touches Lode Runner…</string>
<string name="keypad_preset_loderunner_toast">Start game with Ctrl-K to activate keyboard</string>
<string name="joystick_button_swipe_left_button">Swipe left fire</string>
<string name="joystick_button_swipe_right_button">Swipe right fire</string>
<string name="joystick_button_swipe_left_button_summary">Button to fire on swipe left</string>
<string name="joystick_button_swipe_right_button_summary">Button to fire on swipe right</string>
<string name="key_del">[DEL]</string>
<string name="key_tab">[TAB]</string>
<string name="preferences_email_logs">Email logs</string>
<string name="preferences_email_logs_summary">Email logs to developer…</string>
</resources>

View File

@@ -9,18 +9,24 @@
<string name="crasher_title">Crash emulator</string>
<string name="crash_null">NULL-deref</string>
<string name="crash_java_npe">Java NPE</string>
<string name="crash_sigabrt">SIGABRT</string>
<string name="crash_sigfpe">SIGFPE</string>
<string name="crash_stackcall_overflow">stack call overflow</string>
<string name="crash_stackbuf_overflow">stack buffer overflow</string>
<!-- main options -->
<string name="about_apple2ix">About Apple2ix…</string>
<string name="about_apple2ix_summary">About this software</string>
<string name="about_apple2ix_licenses">Licenses…</string>
<string name="about_apple2ix_licenses_summary">Apple2ix software licenses</string>
<string name="app_name">Apple2ix</string>
<string name="audio_configure">Configure audio…</string>
<string name="audio_configure_summary">Speaker volume, Mockingboard, etc</string>
<string name="audio_latency">Audio latency</string>
<string name="audio_latency_summary">Audio latency in secs</string>
<string name="cancel">Cancel</string>
<string name="color_configure">Video display mode</string>
<string name="color_configure_summary">Video display emulation mode</string>
<string name="color_bw">Black/white</string>
<string name="color_color">Color</string>
<string name="color_interpolated">Interpolated color</string>
@@ -32,16 +38,18 @@
<string name="diskA">Drive 1</string>
<string name="diskB">Drive 2</string>
<string name="disk_eject">Eject</string>
<string name="disk_insert_toast">Inserted disk in drive read-only</string>
<string name="disk_insert_could_not_read">Sorry, could not read the disk image!</string>
<string name="disk_insert_toast_cannot">Cannot insert (not a disk image or state file)</string>
<string name="disk_read_only">Read only</string>
<string name="disk_read_write">Read/write</string>
<string name="disk_selection_newschoool">System chooser</string>
<string name="disk_show_operation">Show Disk ][ operations</string>
<string name="disk_show_operation_summary">Shows when disk drives are reading or writing</string>
<string name="emulation_continue">Continue…</string>
<string name="emulation_settings">Settings…</string>
<string name="emulation_disks">Load disk image…</string>
<string name="emulation_disks">Load disk image</string>
<string name="file_chooser">Choose disk image or state file…</string>
<string name="header_disks">Insert disk:</string>
<string name="header_eject_1">Eject 1</string>
<string name="header_eject_2">Eject 2</string>
<string name="input_current">Current touch device</string>
<string name="input_current_summary">Choose current touch device</string>
<string name="joystick">Joystick</string>
@@ -52,18 +60,20 @@
<string name="joystick_button_button_none">None</string>
<string name="joystick_button_tap_button">Tap fire</string>
<string name="joystick_button_tap_button_summary">Button to fire on tap down</string>
<string name="joystick_button_tapdelay_summary">Joystick button tap delay in secs</string>
<string name="joystick_button_tapdelay_summary">Joystick touch down delay in video frames</string>
<string name="joystick_button_swipe_up_button">Swipe up fire</string>
<string name="joystick_button_swipe_up_button_summary">Button to fire on swipe up</string>
<string name="joystick_button_swipe_down_button">Swipe down fire</string>
<string name="joystick_button_swipe_down_button_summary">Button to fire on swipe down</string>
<string name="joystick_button_threshold_summary">Joystick/keypad button switch threshold in pts</string>
<string name="joystick_button_threshold_summary">Joystick/keypad button switch threshold in pts (max: &#8531; screen height)</string>
<string name="joystick_calibrate">Calibrate…</string>
<string name="joystick_calibrate_summary">Configure and test current settings</string>
<string name="joystick_configure">Configure joystick…</string>
<string name="joystick_configure">Configure touch joystick…</string>
<string name="joystick_configure_summary">Axis touch, buttons, etc</string>
<string name="joystick_axisleft">Joystick/keypad axis on left</string>
<string name="joystick_axisleft_summary">Joystick/keypad axis on left (buttons on right)</string>
<string name="joystick_azimuth_visible">Show joystick/keypad heading</string>
<string name="joystick_azimuth_visible_summary">Shows current axis direction and magnitude</string>
<string name="joystick_visible">Joystick/keypad visibility</string>
<string name="joystick_visible_summary">Show controls overlay when engaged</string>
<string name="key_closed_apple">[ClosedApple]</string>
@@ -82,22 +92,22 @@
<string name="keyboard_choose_alt_summary">Choose alternative customized layout</string>
<string name="keyboard_click_enabled">Enable key click</string>
<string name="keyboard_click_enabled_summary">Enables key click sound if available</string>
<string name="keyboard_configure">Configure keyboard…</string>
<string name="keyboard_configure">Configure touch keyboard…</string>
<string name="keyboard_configure_summary">Transparency, lowercase, custom keys</string>
<string name="keyboard_glyph_scale">Keyboard glyphs scaled 2x</string>
<string name="keyboard_glyph_scale_summary">(Makes keyboard appear less pixelated on large screens)</string>
<string name="keyboard_lowercase_enabled">Enable lowercase</string>
<string name="keyboard_lowercase_enabled_summary">Enable lowercase keys</string>
<string name="keyboard_visibility_active">Visibility when active</string>
<string name="keyboard_visibility_active_summary">Keyboard visibility when active</string>
<string name="keyboard_visibility_active_summary">Keyboard and touch menu visibility when active</string>
<string name="keyboard_visibility_inactive">Visibility when inactive</string>
<string name="keyboard_visibility_inactive_summary">Keyboard visibility when inactive</string>
<string name="keyboard_visibility_inactive_summary">Keyboard and touch menu visibility when inactive</string>
<string name="keypad">Keypad Joystick</string>
<string name="keypad_calibrate">@string/joystick_calibrate</string>
<string name="keypad_calibrate_summary">@string/joystick_calibrate</string>
<string name="keypad_choose">Choose keypad keys…</string>
<string name="keypad_choose_summary">Choose axis and button keys</string>
<string name="keypad_choose_title">Axis &amp; buttons</string>
<string name="keypad_choose_current">Choose XXX Key: </string>
<string name="keypad_configure">Configure keypad joystick…</string>
<string name="keypad_configure">Configure touch keypad joystick…</string>
<string name="keypad_configure_summary">@string/joystick_configure_summary</string>
<string name="keypad_key_axis_c">Center</string>
<string name="keypad_key_axis_dn">Down</string>
@@ -111,39 +121,50 @@
<string name="keypad_key_button_tap">Tap</string>
<string name="keypad_key_button_swipeup">Swipe Up</string>
<string name="keypad_key_button_swipedown">Swipe Down</string>
<string name="keypad_preset_crazy_seafox">Seafox keys ;-)</string>
<string name="keypad_preset_seafox">Seafox keys…</string>
<string name="keypad_preset_custom">Choose custom…</string>
<string name="keypad_preset_arrows_space">&#8593;,&#8592;,&#8594;,&#8595;, tap spacebar</string>
<string name="keypad_preset_az_left_right_space">A,Z,&#8592;,&#8594;, tap spacebar</string>
<string name="keypad_preset_ijkm_space">I,J,K,M, tap spacebar</string>
<string name="keypad_preset_left_right_space">&#8592;,&#8594;, tap spacebar</string>
<string name="keypad_preset_wadx_space">W,A,D,X, tap spacebar</string>
<string name="keypad_repeat_summary">Key repeat threshold in secs</string>
<string name="menu_disks">Load disk image…</string>
<string name="menu_disks_summary">Insert a Disk ][ image file</string>
<string name="keypad_preset_arrows_space">(↑ ← → ↓), tap spacebar</string>
<string name="keypad_preset_az_left_right_space">(A Z ← →), tap spacebar</string>
<string name="keypad_preset_ijkm_space">(I J K M), tap spacebar</string>
<string name="keypad_preset_left_right_space">(← →), tap spacebar</string>
<string name="keypad_preset_wadx_space">(W A D X), tap spacebar</string>
<string name="keypad_tapdelay_summary">Keypad touch down delay in secs</string>
<string name="menu_disks">Load image or state file…</string>
<string name="menu_disks_summary">Insert Disk ][ image or state file</string>
<string name="menu_settings">Emulator settings…</string>
<string name="menu_settings_summary">General settings, joystick, keyboard</string>
<string name="mockingboard_disabled_title">Mockingboard disabled</string>
<string name="mockingboard_disabled_mesg">Mockingboard could not be enabled</string>
<string name="mockingboard_enable">Enable Mockingboard</string>
<string name="mockingboard_enable_summary">Revision C in Slot 4/5 (may require restart)</string>
<string name="mockingboard_enable_summary">Revision C in Slot 4/5 (may require restart) (may consume extra battery)</string>
<string name="mockingboard_volume">Mockingboard volume</string>
<string name="mockingboard_volume_summary">Set the Mockingboard volume</string>
<string name="mode_landscape">Landscape</string>
<string name="mode_landscape_summary">Enable landscape or portrait mode</string>
<string name="no">No</string>
<string name="ok">OK</string>
<string name="portrait_calibrate">Calibrate portrait mode</string>
<string name="portrait_calibrate_framebuffer">Display position</string>
<string name="portrait_calibrate_keyboard_height">Keyboard height</string>
<string name="portrait_calibrate_keyboard_position">Keyboard position</string>
<string name="portrait_calibrate_summary">Adjust keyboard size, framebuffer position, etc</string>
<string name="preferences_reset_title">Reset preferences</string>
<string name="preferences_reset_summary">Reset preferences to defaults and quit emulator</string>
<string name="preferences_reset_really">Really reset and quit?</string>
<string name="preferences_reset_warning">You will lose your settings</string>
<string name="quit">Quit</string>
<string name="quit_reboot">Reboot or quit emulator…</string>
<string name="quit_reboot_choice">Reboot or quit?</string>
<string name="quit_reboot">Reset or quit emulator…</string>
<string name="quit_reboot_choice">Reset, reboot or quit?&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#13;&#10;</string>
<string name="reboot">Reboot</string>
<string name="reset">Reset</string>
<string name="reset_preferences">Reset settings</string>
<string name="reset_self_test">Self Test</string>
<string name="reset_soft">Soft</string>
<string name="restore">Quick restore</string>
<string name="save">Quick save</string>
<string name="saverestore">Save &amp; restore…</string>
<string name="saverestore_choice">Save current state or restore previous?</string>
<string name="saverestore_summary">Quick save and restore</string>
<string name="saverestore_summary">Save and restore emulator state</string>
<string name="skip">Skip&#8594;</string>
<string name="speaker_volume">Speaker volume</string>
<string name="speaker_volume_summary">Set the speaker volume</string>
@@ -152,11 +173,49 @@
<string name="settings_advanced_summary">Warning: these settings may degrade performance</string>
<string name="settings_advanced_joystick">Advanced joystick/keypad settings</string>
<string name="settings_advanced_joystick_summary">Advanced settings and performance tuning</string>
<string name="storage">/storage/</string>
<string name="state_not_restored">Error restoring state…</string>
<string name="touch_menu_enable">Enable touch menus</string>
<string name="touch_menu_enable_summary">Enables soft menu buttons in top screen corners</string>
<string name="touch_menu_visibility">Touch menu visibility</string>
<string name="touch_menu_visibility_summary">Touch menu visibility when inactive</string>
<string name="video_configure">Configure video…</string>
<string name="video_configure_summary">Color settings</string>
<string name="video_configure_summary">Landscape/portrait, video modes, colors, etc</string>
<string name="touch_device_color">Configure touch device color</string>
<string name="touch_device_color_summary">Configure color of HUD elements</string>
<string name="touch_device_color_configure">Configure HUD color…</string>
<string name="color_red_on_black">Red on black</string>
<string name="color_green_on_black">Green on black</string>
<string name="color_blue_on_black">Blue on black</string>
<string name="color_white_on_black">White on black</string>
<string name="color_monitor">Color Monitor</string>
<string name="color_tv_mono">Monochrome TV</string>
<string name="color_tv">Color TV</string>
<string name="show_half_scanlines">Show half scanlines</string>
<string name="mono_configure">Monocolor mode</string>
<string name="mono_configure_summary">Configure monochrome color</string>
<string name="color_green">Green screen</string>
<string name="color_mono">Monochrome</string>
<string name="disk_fast_operation">Disk fast loading (EXPERIMENTAL)</string>
<string name="disk_fast_operation_summary">Quickly load disk images (may negatively affect device battery life)</string>
<string name="show_half_scanlines_summary">Renders pixel vertical divisions</string>
<string name="release_notes">Release notes</string>
<string name="release_notes_summary">View notes for this release</string>
<string name="keyboard_duotouch_enabled">Enable dual-touch</string>
<string name="keyboard_duotouch_enabled_summary">Support two-thumb input</string>
<string name="keypad_preset_qaz_left_right_space">(A Z ← →), tap spacebar, Q up</string>
<string name="keypad_autorepeat_fast">Keypad Immediate Autorepeat</string>
<string name="keypad_autorepeat_fast_summary">Allows immediate auto-repeat (avoids original 534-801 millis delay)</string>
<string name="string_frames">Frames</string>
<string name="string_millis">Millis</string>
<string name="keypad_preset_robotron">Robotron keys…</string>
<string name="keypad_preset_loderunner">Lode Runner keys…</string>
<string name="keypad_preset_loderunner_toast">Start game with Ctrl-K to activate keyboard</string>
<string name="joystick_button_swipe_left_button">Swipe left fire</string>
<string name="joystick_button_swipe_right_button">Swipe right fire</string>
<string name="joystick_button_swipe_right_button_summary">Button to fire on swipe right</string>
<string name="joystick_button_swipe_left_button_summary">Button to fire on swipe left</string>
<string name="key_del">[DEL]</string>
<string name="key_tab">[TAB]</string>
<string name="preferences_email_logs">Email logs</string>
<string name="preferences_email_logs_summary">Email logs to developer…</string>
</resources>

View File

@@ -1,8 +1,10 @@
<resources>
<!-- Base application theme. -->
<!-- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> ... this freaks-out CLI builds -->
<!-- Customize your theme here. -->
<!-- </style> -->
<style name="Theme.AppCompat.NoActionBar.FullScreen" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
</full-backup-content>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>

View File

@@ -5,7 +5,6 @@
"_comment" : "hex code for special glyphs",
"_AA" : "b5",
"_CTRL": "b3",
"_XX" : "9b",
"_ESC" : "bc",
"_OA" : "81",
"_CA" : "80",
@@ -20,7 +19,7 @@
["_AA", "", "_CTRL", "", "_ESC", "", "_OA", "", "_CA", ""],
[ "", "", "", "", "", "", "", "", "", ""],
[ "", "", "", "", "", "_UP", "", "", "", ""],
[ "", "", "", "", "_LT", "_XX", "_RT", "", "", ""],
[ "", "", "", "", "_LT", "_AA", "_RT", "", "", ""],
[ "", "", "", "", "", "_DN", "", "", "", ""],
[ "", "", "", "", "", "", "", "", "", ""]
]

View File

@@ -0,0 +1,24 @@
[
"Alt keyboard optimized for 2400AD",
{
"_comment" : "hex code for special glyphs",
"_AA" : "b5",
"_ESC" : "bc",
"_SP" : "b1",
"_RET" : "8d",
"_LT" : "88",
"_RT" : "95",
"_UP" : "8b",
"_DN" : "8a"
},
[ "", "1", "2", "3", "4", "5", "6", "7", "8", "" ],
[ "_AA", "", "", "", "", "", "", "", "", "Y" ],
[ "S", "", "", "", "", "", "", "", "", "N" ],
[ "R", "", "", "", "", "", "", "", "", "U" ],
[ "W" , "", "", "", "", "", "", "", "", "D" ],
[ "", "_RET", "", "", "", "", "", "", "", "F" ],
[ "_LT", "_SP", "_RT", "", "", "", "", "", "", "L" ],
[ "", "_DN", "", "G", "B", "O", "C", "T", "P","A" ]
]

View File

@@ -9,11 +9,11 @@
"_SP" : "b1"
},
[ "reserved for future use" ],
[ "reserved for future use" ],
[ "", "", "", "", "", "", "", "", "", "" ],
[ "_AA", "", "", "", "", "", "", "", "", "" ],
[ "", "", "", "", "", "", "", "", "", "" ],
[ "", "", "", "", "", "", "", "", "", "" ],
[ "", "", "", "", "", "", "", "", "", "" ],
[ "Q", "", "", "", "", "", "", "", "A", "" ],
[ "", "", "", "", "", "", "", "_LT", "", "_RT" ],
[ "_SP", "", "", "", "", "", "", "", "Z", "" ]

View File

@@ -0,0 +1,24 @@
[
"Alt keyboard optimized for Deathlord",
{
"_comment" : "hex code for special glyphs",
"_AA" : "b5",
"_ESC" : "bc",
"_RET" : "8d",
"_UP" : "8b",
"_LT" : "88",
"_RT" : "95",
"_DN" : "8a",
"_SP" : "b1"
},
[ "", "", "", "1", "2", "3", "4" , "5" , "6", "" ],
[ "Z", "", "", "", "", "&", "'", "(", ")", "$" ],
["_AA", "", "", "", "", "", "P", "", "^", "#"],
[ "N", "", "", "", "", "", "T", "", "U", "Y"],
[ "?", "", "", "", "", "", "_ESC", "", "_SP", "" ],
[ "?", "", "", "", "", "", "", "I", "", "_UP" ],
[ "C", "", "S", "R", "G", "V", "J", "", "K", "_RET"],
[ "A", "", "O", "E", "F", "B", "", "M", "", "_DN" ]
]

View File

@@ -8,9 +8,9 @@
"_SP" : "b1"
},
["reserved for future use"],
["reserved for future use"],
["_AA", "", "", "", "", "", "", "", "", "D"],
[ "Q", "", "", "", "", "", "", "", "", "" ],
[ "", "", "", "", "", "", "", "", "", "V"],
[ "_AA", "", "", "", "", "", "", "", "", "D"],
[ "_ESC", "", "", "", "", "", "", "", "", "G"],
[ "Y", "", "", "", "", "", "", "", "", "" ],
[ "N", "", "", "", "", "", "", "I", "O", "P"],

View File

@@ -5,19 +5,19 @@
"_comment" : "hex code for special glyphs",
"_AA" : "b5",
"_ESC" : "bc",
"_UP" : "8b",
"_UP" : "8d",
"_LT" : "88",
"_RT" : "95",
"_DN" : "8a",
"_SP" : "b1"
},
["reserved for future use"],
["reserved for future use"],
[ "Q", "", "", "", "", "", "", "", "K", "X"],
[ "P", "", "", "", "", "", "", "", "D", "B"],
["_AA", "", "", "", "", "", "1", "2", "3", "4"],
[ "N", "", "", "", "", "", "5", "6", "7", "8"],
[ "Y", "", "", "", "", "", "Z", "G", "", "_ESC"],
[ "S", "", "", "", "", "", "C", "", "_UP", "" ],
[ "", "", "", "", "", "", "", "_LT", "", "_RT"],
[ "A", "", "T", "O", "", "", "_SP", "", "_DN", ""]
[ "", "", "", "", "", "", "", "_LT", "", "_RT"],
[ "A", "", "T", "O", "F", "E", "_SP", "", "_DN", ""]
]

View File

@@ -0,0 +1,23 @@
[
"Alt keyboard optimized for Ultima(tm) 5",
{
"_comment" : "hex code for special glyphs",
"_AA" : "b5",
"_ESC" : "bc",
"_UP" : "8d",
"_LT" : "88",
"_RT" : "95",
"_DN" : "8a",
"_SP" : "b1"
},
[ "Q", "", "", "", "", "", "", "", "", "" ],
[ "P", "", "", "", "", "", "", "", "", "" ],
["_AA", "", "", "", "", "", "", "", "K", "X"],
[ "N", "", "", "", "", "", "L", "B", "_SP", "V"],
[ "Y", "", "", "", "", "", "Z", "G", "", "_ESC"],
[ "S", "", "", "", "", "", "C", "", "_UP", "" ],
[ "", "", "", "", "", "", "", "_LT", "", "_RT"],
[ "A", "", "T", "O", "J", "F", "E", "", "_DN", ""]
]

View File

@@ -0,0 +1,22 @@
[
"Alt keyboard optimized for Windwalker",
{
"_comment" : "hex code for special glyphs",
"_AA" : "b5",
"_ESC" : "bc",
"_SP" : "b1",
"_RET" : "8d",
"_UP" : "8b",
"_DN" : "8a"
},
[ "", "", "", "", "", "", "", "", "", "" ],
[ "", "", "", "", "", "", "", "", "", "" ],
[ "_AA", "", "", "", "", "", "", "", "", "" ],
[ "_ESC", "", "", "", "", "", "", "", "", "_RET" ],
[ "_UP", "", "", "", "", "", "", "", "", "" ],
[ "_DN", "", "", "", "", "", "", "I", "O", "P"],
[ "Y", "", "A", "S", "", "", "", "K", "L", ";"],
[ "N", "", "Z", "X", "", "", "", ",", ".", "/"]
]

View File

@@ -0,0 +1,30 @@
Apple2ix (A2IX) 2.1.0-RC2 for Android
The major change here is that I now provide full 64bit native builds for arm64-v8a and x86_64 devices. (This is a new G00G requirement for publishing on the Play Store). It frankly isn't something that I would have wanted to foist upon you, dear user! But they are forcing my hand, so ... c'est la vie!
Anecdotally, I have heard about certain devices that claim to be arm64-v8a, but which DO NOT run 64bit code efficiently. These devices would have been better off continuing to run the armeabi-v7a build of A2IX. But there's nothing I can do about this, since the Play Store serves the specific build it thinks best. So if you are one of the unlucky owners of such a device and A2IX seems horrendously sluggish, (and if you're an adventurous power-user), you could side-load the armeabi-v7a semi-official release from the GitHub project page ;)
MENU INTERFACE:
- Migrated menu system to newer-ish Dark Holo theme
- Moved some preferences around a bit for clarity
TOUCH KEYBOARD:
- New preference for dual-thumb touch keyboard handling
- More alternate keyboard presets for old-school RPGs
- Tapping Open/Closed-Apple keys now generate joystick button events
TOUCH JOYSTICKS:
- Keypad Joystick: Improved emulation fidelity for the keyboard auto-repeat circuitry, tied to the emulated video refresh. (In plain English: improved twitch-response ;)
- Keypad Joystick: Full left and right side rosette key configurations (18 total configurable keys)
- Keypad Joystick: More presets for popular games (L0de Runner, R0b0tr0n 2084, ...)
- Keypad+Joystick: Improved calibration mode showing axis/key values
- Keypad+Joystick: Button/key switch threshold now configurable down to zero minimum (improves twitch-reponse)
- Joystick: Can now configure button to fire on left/right swipe (instead of just tap and swipe up/down)
M0AR INFO:
- Apple2ix for Android home page : https://deadc0de.org/apple2ix/android
- Play Store beta-build opt-in URL: https://play.google.com/apps/testing/org.deadc0de.apple2ix.basic

View File

@@ -0,0 +1 @@
../../../src/video/SolidColor.fsh

View File

@@ -0,0 +1 @@
../../../src/video/SolidColor.vsh

View File

@@ -3,9 +3,10 @@
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
classpath 'com.android.tools.build:gradle:3.5.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -15,5 +16,6 @@ buildscript {
allprojects {
repositories {
jcenter()
google()
}
}

View File

@@ -17,4 +17,5 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useDeprecatedNdk=true
android.enableJetifier=true
android.useAndroidX=true

View File

@@ -1,6 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
#Sun Oct 27 16:51:42 PDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

View File

@@ -1,4 +1,4 @@
APP_ABI := armeabi armeabi-v7a x86
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
# Do not change APP_PLATFORM if we care about Gingerbread (2.3.3) devices! We must compile against android-10,
# otherwise we may encounter runtime load-library errors from symbols that should have been inlined against older

View File

@@ -40,6 +40,19 @@ void android_keycode_to_emulator(int keyCode, int metaState, bool pressed) {
break;
}
switch (keyCode) {
case KEYCODE_BUTTON_A:
case KEYCODE_BUTTON_X:
case KEYCODE_BUTTON_L1:
joydriver_setButton0Pressed(pressed);
return;
case KEYCODE_BUTTON_B:
case KEYCODE_BUTTON_Y:
case KEYCODE_BUTTON_R1:
joydriver_setButton1Pressed(pressed);
return;
}
switch (keyCode) {
case KEYCODE_0:
key = _is_shifted(metaState) ? ')' : keyCode + ASCII_0_OFFSET;
@@ -230,16 +243,20 @@ void android_keycode_to_emulator(int keyCode, int metaState, bool pressed) {
}
} while (0);
LOG("keyCode:%08x -> key:%02x ('%c') metaState:%08x", keyCode, key, key, metaState);
if (key < 0) {
return;
}
//LOG("keyCode:%08x -> key:%02x ('%c') metaState:%08x", keyCode, key, key, metaState);
if (isASCII && _is_ctrl(metaState)) {
key = c_keys_ascii_to_scancode(key);
c_keys_handle_input(key, true, false);
key = keys_ascii2Scancode(key);
keys_handleInput(key, /*is_pressed:*/true, /*is_ascii:*/false);
isASCII = false;
pressed = false;
}
assert(key < 0x80);
c_keys_handle_input(key, pressed, isASCII);
keys_handleInput(key, pressed, isASCII);
}

View File

@@ -98,6 +98,8 @@
#define KEYCODE_BUTTON_L2 0x68
#define KEYCODE_BUTTON_R1 0x67
#define KEYCODE_BUTTON_R2 0x69
#define KEYCODE_BUTTON_X 0x63
#define KEYCODE_BUTTON_Y 0x64
#define META_ALT_LEFT_ON 0x00000010
#define META_ALT_RIGHT_ON 0x00000020

View File

@@ -28,13 +28,19 @@ LOCAL_SRC_FILES := jnicrash.c $(APPLE2_SRC_PATH)/breakpad.C
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) $(BREAKPAD_CFLAGS)
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
ifeq ($(TARGET_ARCH_ABI),x86)
ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI),x86 x86_64))
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
LOCAL_CFLAGS += -DNO_UNDERSCORES=1
else
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
endif
ifeq ($(BUILD_MODE),release)
LOCAL_CFLAGS += -DNDEBUG=1
else
LOCAL_CFLAGS += -g
endif
ifeq ($(EMBEDDED_STACKWALKER),1)
LOCAL_CPPFLAGS += -DEMBEDDED_STACKWALKER=1
else

View File

@@ -1,8 +1,8 @@
#!/bin/sh
#!/bin/bash
package_id="org.deadc0de.apple2ix.basic"
apple2_src_path=apple2ix-src
glue_srcs="$apple2_src_path/disk.c $apple2_src_path/misc.c $apple2_src_path/display.c $apple2_src_path/vm.c $apple2_src_path/cpu-supp.c $apple2_src_path/audio/speaker.c $apple2_src_path/audio/mockingboard.c"
glue_srcs="$apple2_src_path/cpu-supp.c $apple2_src_path/disk.c $apple2_src_path/display.c $apple2_src_path/vm.c $apple2_src_path/audio/speaker.c $apple2_src_path/audio/mockingboard.c"
usage() {
if test "$(basename $0)" = "clean" ; then
@@ -12,7 +12,7 @@ usage() {
echo "$0"
echo " # uninstalls $package_id"
else
echo "$0 [build] [load|debug]"
echo "$0 [build|release] [load|debug]"
echo " # default builds $package_id and then load or debug"
fi
exit 0
@@ -22,6 +22,14 @@ export EMBEDDED_STACKWALKER=1
while test "x$1" != "x"; do
case "$1" in
"build")
do_build=1
;;
"release")
do_release=1
;;
"debug")
do_debug=1
;;
@@ -45,6 +53,11 @@ while test "x$1" != "x"; do
shift
done
if test "x$do_build" = "x1" -a "x$do_release" = "x1" ; then
echo "Must specify either build or release"
usage
fi
set -x
if test "$(basename $0)" = "clean" ; then
@@ -74,91 +87,115 @@ if test "$(basename $0)" = "uninstall" ; then
exit 0
fi
#CC=`which clang`
CC=`which gcc`
CFLAGS="-std=gnu11"
# ROMz
$CC $CFLAGS -o $apple2_src_path/genrom $apple2_src_path/genrom.c && \
$apple2_src_path/genrom $apple2_src_path/rom/apple_IIe.rom $apple2_src_path/rom/slot6.rom > $apple2_src_path/rom.c
# font
$CC $CFLAGS -o $apple2_src_path/genfont $apple2_src_path/genfont.c && \
$apple2_src_path/genfont < $apple2_src_path/font.txt > $apple2_src_path/font.c
# glue
$apple2_src_path/x86/genglue $glue_srcs > $apple2_src_path/x86/glue.S
$apple2_src_path/arm/genglue $glue_srcs > $apple2_src_path/arm/glue.S
if test "$(basename $0)" = "testcpu" ; then
ln -s testcpu.mk Android.mk
elif test "$(basename $0)" = "testvm" ; then
ln -s testvm.mk Android.mk
elif test "$(basename $0)" = "testdisplay" ; then
ln -s testdisplay.mk Android.mk
elif test "$(basename $0)" = "testdisk" ; then
ln -s testdisk.mk Android.mk
else
elif test "$(basename $0)" = "testdisplay" ; then
ln -s testdisplay.mk Android.mk
elif test "$(basename $0)" = "testprefs" ; then
ln -s testprefs.mk Android.mk
elif test "$(basename $0)" = "testtrace" ; then
ln -s testtrace.mk Android.mk
elif test "$(basename $0)" = "testui" ; then
ln -s testui.mk Android.mk
elif test "$(basename $0)" = "testvm" ; then
ln -s testvm.mk Android.mk
elif test "$(basename $0)" = "apple2ix" ; then
ln -s apple2ix.mk Android.mk
else
echo "OOPS, unsure of what to build"
exit 1
fi
###############################################################################
# build native sources
ndk-build V=1 NDK_MODULE_PATH=. NDK_DEBUG=1 # NDK_TOOLCHAIN_VERSION=clang
ret=$?
if test "x$ret" != "x0" ; then
exit $ret
if test "x$do_build" = "x1" -o "x$do_release" = "x1" ; then
#CC=`which clang`
CC=`which gcc`
CFLAGS="-std=gnu11"
# ROMz
$CC $CFLAGS -o $apple2_src_path/genrom $apple2_src_path/genrom.c && \
$apple2_src_path/genrom $apple2_src_path/rom/apple_IIe.rom $apple2_src_path/rom/slot6.rom > $apple2_src_path/rom.c
# font
$CC $CFLAGS -o $apple2_src_path/genfont $apple2_src_path/genfont.c && \
$apple2_src_path/genfont < $apple2_src_path/font.txt > $apple2_src_path/font.c
# bridge trampoline generation
TARGET_ARCH=x86 $apple2_src_path/genglue.sh $glue_srcs > $apple2_src_path/x86/glue.S
TARGET_ARCH=arm $apple2_src_path/genglue.sh $glue_srcs > $apple2_src_path/arm/glue.S
if test "x$do_build" = "x1" ; then
export BUILD_MODE=debug
ndk-build V=1 NDK_MODULE_PATH=. NDK_DEBUG=1 NDK_TOOLCHAIN_VERSION=4.9
ret=$?
if test "x$ret" != "x0" ; then
exit $ret
fi
else
export BUILD_MODE=release
ndk-build V=1 NDK_MODULE_PATH=. NDK_DEBUG=0 NDK_TOOLCHAIN_VERSION=4.9
ret=$?
if test "x$ret" != "x0" ; then
exit $ret
fi
fi
# Symbolicate and move symbols file into location to be deployed on device
SYMFILE=libapple2ix.so.sym
ARCHES_TO_SYMBOLICATE='armeabi-v7a arm64-v8a x86 x86_64'
for arch in $ARCHES_TO_SYMBOLICATE ; do
SYMDIR=../assets/symbols/$arch/libapple2ix.so
# remove old symbols (if any)
/bin/rm -rf $SYMDIR
# Run Breakpad's dump_syms
host_arch=`uname -s`
../../externals/bin/$host_arch/dump_syms ../obj/local/$arch/libapple2ix.so > $SYMFILE
ret=$?
if test "x$ret" != "x0" ; then
echo "OOPS, dump_syms failed for $arch"
exit $ret
fi
# strip to the just the numeric id in the .sym header and verify it makes sense
sym_id=$(head -1 $SYMFILE | cut -d ' ' -f 4)
sym_id_check=$(echo $sym_id | wc -c | tr -d ' ' )
if test "x$sym_id_check" != "x34" ; then
echo "OOPS symbol header not expected size, meat-space intervention needed =P"
exit 1
fi
sym_id_check=$(echo $sym_id | tr -d 'A-Fa-f0-9' | wc -c | tr -d ' ' )
if test "x$sym_id_check" != "x1" ; then
echo "OOPS unexpected characters in symbol header, meat-space intervention needed =P"
exit 1
fi
mkdir -p $SYMDIR/$sym_id
ret=$?
if test "x$ret" != "x0" ; then
echo "OOPS, could not create symbols directory for arch:$arch and sym_id:$sym_id"
exit $ret
fi
/bin/mv $SYMFILE $SYMDIR/$sym_id/
ret=$?
if test "x$ret" != "x0" ; then
echo "OOPS, could not move $SYMFILE to $SYMDIR/$sym_id/"
exit $ret
fi
done
fi
###############################################################################
# Symbolicate and move symbols file into location to be deployed on device
SYMFILE=libapple2ix.so.sym
ARCHES_TO_SYMBOLICATE='armeabi armeabi-v7a x86'
for arch in $ARCHES_TO_SYMBOLICATE ; do
SYMDIR=../assets/symbols/$arch/libapple2ix.so
# remove old symbols (if any)
/bin/rm -rf $SYMDIR
# Run Breakpad's dump_syms
../../externals/bin/dump_syms ../obj/local/$arch/libapple2ix.so > $SYMFILE
ret=$?
if test "x$ret" != "x0" ; then
echo "OOPS, dump_syms failed for $arch"
exit $ret
fi
# strip to the just the numeric id in the .sym header and verify it makes sense
sym_id=$(head -1 $SYMFILE | cut -d ' ' -f 4)
sym_id_check=$(echo $sym_id | wc -c)
if test "x$sym_id_check" != "x34" ; then
echo "OOPS symbol header not expected size, meat-space intervention needed =P"
exit 1
fi
sym_id_check=$(echo $sym_id | tr -d 'A-Fa-f0-9' | wc -c)
if test "x$sym_id_check" != "x1" ; then
echo "OOPS unexpected characters in symbol header, meat-space intervention needed =P"
exit 1
fi
mkdir -p $SYMDIR/$sym_id
ret=$?
if test "x$ret" != "x0" ; then
echo "OOPS, could not create symbols directory for arch:$arch and sym_id:$sym_id"
exit $ret
fi
/bin/mv $SYMFILE $SYMDIR/$sym_id/
ret=$?
if test "x$ret" != "x0" ; then
echo "OOPS, could not move $SYMFILE to $SYMDIR/$sym_id/"
exit $ret
fi
done
###############################################################################
# usually we should build the Java stuff from within Android Studio
if test "x$do_load" = "x1" ; then

View File

@@ -17,6 +17,8 @@ enum {
CRASH_NULL_DEREF,
CRASH_STACKCALL_OVERFLOW,
CRASH_STACKBUF_OVERFLOW,
CRASH_SIGABRT,
CRASH_SIGFPE,
// MOAR!
};
@@ -68,6 +70,21 @@ static volatile int __attribute__((noinline)) _crash_stackbuf_overflow(void) {
return getpid();
}
static void _crash_sigabrt(void) {
kill(getpid(), SIGABRT);
__builtin_unreachable();
}
static void _crash_sigfpe(void) {
static volatile int foo = 2;
static volatile int bar = 0;
while (foo >= 0) {
--foo;
bar = 2/foo;
}
__builtin_unreachable();
}
void Java_org_deadc0de_apple2ix_Apple2CrashHandler_nativePerformCrash(JNIEnv *env, jclass cls, jint crashType) {
#warning FIXME TODO ... we should turn off test codepaths in release build =D
LOG("... performing crash of type : %d", crashType);
@@ -85,6 +102,14 @@ void Java_org_deadc0de_apple2ix_Apple2CrashHandler_nativePerformCrash(JNIEnv *en
_crash_stackbuf_overflow();
break;
case CRASH_SIGABRT:
_crash_sigabrt();
break;
case CRASH_SIGFPE:
_crash_sigfpe();
break;
default:
// unknown crasher, just abort ...
abort();
@@ -107,21 +132,25 @@ void Java_org_deadc0de_apple2ix_Apple2CrashHandler_nativeProcessCrash(JNIEnv *en
do {
outputFILE = TEMP_FAILURE_RETRY_FOPEN(fopen(outputPath, "w"));
if (!outputFILE) {
ERRLOG("could not open %s", outputPath);
LOG("could not open %s", outputPath);
break;
}
if (android_armArchV7A) {
asprintf(&symbolsPath, "%s/symbols/armeabi-v7a", data_dir);
if (android_arm64Arch) {
ASPRINTF(&symbolsPath, "%s/symbols/arm64-v8a", data_dir);
} else if (android_armArchV7A || android_armArch) {
ASPRINTF(&symbolsPath, "%s/symbols/armeabi-v7a", data_dir);
} else if (android_x86_64) {
ASPRINTF(&symbolsPath, "%s/symbols/x86_64", data_dir);
} else if (android_x86) {
asprintf(&symbolsPath, "%s/symbols/x86", data_dir);
} else /*if (android_armArch)*/ {
asprintf(&symbolsPath, "%s/symbols/armeabi", data_dir);
} /*else { moar archs ... } */
ASPRINTF(&symbolsPath, "%s/symbols/x86", data_dir);
} else {
LOG("unknown symbols architecture!");
}
bool success = crashHandler->processCrash(crashPath, symbolsPath, outputFILE);
if (!success) {
RELEASE_LOG("CRASH REPORT PROCESSING FAILED ...");
LOG("CRASH REPORT PROCESSING FAILED ...");
}
} while (0);

View File

@@ -11,6 +11,7 @@
#include "common.h"
#include "androidkeys.h"
#include "json_parse_private.h"
#include <cpu-features.h>
#include <jni.h>
@@ -47,7 +48,7 @@ typedef enum lifecycle_seq_t {
static lifecycle_seq_t appState = APP_RUNNING;
#if TESTING
static void _run_tests(void) {
static void _start_tests(void) {
char *local_argv[] = {
"-f",
NULL
@@ -56,20 +57,41 @@ static void _run_tests(void) {
for (char **p = &local_argv[0]; *p != NULL; p++) {
++local_argc;
}
#if defined(TEST_CPU)
#if TEST_CPU
// Currently this test is the only one that runs as a black screen
extern int test_cpu(int, char *[]);
test_cpu(local_argc, local_argv);
tkill(getpid(), SIGKILL); // and we're done ...
#elif defined(TEST_VM)
extern int test_vm(int, char *[]);
test_vm(local_argc, local_argv);
#elif defined(TEST_DISPLAY)
extern int test_display(int, char *[]);
test_display(local_argc, local_argv);
#elif defined(TEST_DISK)
extern int test_disk(int, char *[]);
kill(getpid(), SIGKILL); // and we're done ...
#endif
cpu_pause();
emulator_start();
while (cpu_thread_id == 0) {
sleep(1);
}
cpu_resume();
#if TEST_DISK
extern void test_disk(int, char *[]);
test_disk(local_argc, local_argv);
#elif TEST_DISPLAY
extern void test_display(int, char *[]);
test_display(local_argc, local_argv);
#elif TEST_PREFS
extern void test_prefs(int, char *[]);
test_prefs(local_argc, local_argv);
#elif TEST_TRACE
extern void test_trace(int, char *[]);
test_trace(local_argc, local_argv);
#elif TEST_UI
extern void test_ui(int, char *[]);
test_ui(local_argc, local_argv);
#elif TEST_VM
extern void test_vm(int, char *[]);
test_vm(local_argc, local_argv);
#elif TEST_CPU
// handled above ...
#else
# error "OOPS, no tests specified"
#endif
@@ -101,8 +123,9 @@ static void discover_cpu_family(void) {
AndroidCpuFamily family = android_getCpuFamily();
uint64_t features = android_getCpuFeatures();
if (family == ANDROID_CPU_FAMILY_X86) {
android_x86 = true;
if (family == ANDROID_CPU_FAMILY_X86 || family == ANDROID_CPU_FAMILY_X86_64) {
android_x86 = (family == ANDROID_CPU_FAMILY_X86);
android_x86_64 = (family == ANDROID_CPU_FAMILY_X86_64);
if (features & ANDROID_CPU_X86_FEATURE_SSSE3) {
LOG("nANDROID_CPU_X86_FEATURE_SSSE3");
android_x86SSSE3Enabled = true;
@@ -133,16 +156,14 @@ static void discover_cpu_family(void) {
LOG("ANDROID_CPU_ARM_FEATURE_LDREX_STREX");
}
} else if (family == ANDROID_CPU_FAMILY_ARM64) {
#warning FIXME TODO ...
//android_arm64Arch = true;
android_armArchV7A = true;
android_arm64Arch = true;
}
}
// ----------------------------------------------------------------------------
// JNI functions
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jobject obj, jstring j_dataDir, jint sampleRate, jint monoBufferSize, jint stereoBufferSize) {
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jclass cls, jstring j_dataDir, jint sampleRate, jint monoBufferSize, jint stereoBufferSize) {
const char *dataDir = (*env)->GetStringUTFChars(env, j_dataDir, 0);
// Android lifecycle can call onCreate() multiple times...
@@ -158,10 +179,16 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jobje
int pagesize = getpagesize();
LOG("PAGESIZE IS : %d", pagesize);
data_dir = strdup(dataDir);
data_dir = STRDUP(dataDir);
if (crashHandler && crashHandler->init) {
crashHandler->init(data_dir);
}
char *home = NULL;
ASPRINTF(&home, "HOME=%s", data_dir);
if (home) {
putenv(home);
LEAK(home);
}
(*env)->ReleaseStringUTFChars(env, j_dataDir, dataDir);
LOG("data_dir : %s", data_dir);
@@ -170,60 +197,63 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jobje
android_monoBufferSubmitSizeSamples = (unsigned long)monoBufferSize;
android_stereoBufferSubmitSizeSamples = (unsigned long)stereoBufferSize;
joydriver_setClampBeyondRadius(true);
//#define DO_CPU65_TRACING 1
#if DO_CPU65_TRACING
# warning !!!!!!!!!! this will quickly eat up disk space !!!!!!!!!!
char *trfile = NULL;
ASPRINTF(&trfile, "%s/%s", data_dir, "cpu_trace.txt");
cpu65_trace_begin(trfile);
FREE(trfile);
#endif
#if TESTING
assert(cpu_thread_id == 0 && "CPU thread must not be initialized yet...");
_run_tests();
// CPU thread is started from testsuite (if needed)
_start_tests();
#else
cpu_pause();
emulator_start();
#endif
}
void Java_org_deadc0de_apple2ix_Apple2View_nativeGraphicsChanged(JNIEnv *env, jclass cls, jint width, jint height) {
// WARNING : this can happen on non-GL thread
void Java_org_deadc0de_apple2ix_Apple2View_nativeGraphicsInitialized(JNIEnv *env, jclass cls) {
LOG("...");
video_backend->reshape(width, height);
}
void Java_org_deadc0de_apple2ix_Apple2View_nativeGraphicsInitialized(JNIEnv *env, jclass cls, jint width, jint height) {
// WANRING : this needs to happen on the GL thread only
LOG("width:%d height:%d", width, height);
_video_setRenderThread(pthread_self()); // by definition, this method is called on the render thread ...
video_shutdown();
video_backend->reshape(width, height);
video_backend->init((void *)0);
video_init();
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationResume(JNIEnv *env, jobject obj) {
jboolean Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationResume(JNIEnv *env, jclass cls) {
if (!cpu_isPaused()) {
return;
return false;
}
LOG("...");
#if TESTING
// test driver thread is managing CPU
#else
cpu_resume();
#endif
return true;
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationPause(JNIEnv *env, jobject obj) {
jboolean Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationPause(JNIEnv *env, jclass cls) {
if (appState != APP_RUNNING) {
return;
return false;
}
#if DO_CPU65_TRACING
cpu65_trace_checkpoint();
#endif
disk6_flush(0);
disk6_flush(1);
if (cpu_isPaused()) {
return;
return false;
}
LOG("...");
#if TESTING
// test driver thread is managing CPU
#else
cpu_pause();
#endif
prefs_save();
return true;
}
void Java_org_deadc0de_apple2ix_Apple2View_nativeRender(JNIEnv *env, jclass cls) {
@@ -237,6 +267,7 @@ void Java_org_deadc0de_apple2ix_Apple2View_nativeRender(JNIEnv *env, jclass cls)
return;
}
//#define FPS_LOG 1
#if FPS_LOG
static uint32_t prevCount = 0;
static uint32_t idleCount = 0;
@@ -254,47 +285,58 @@ void Java_org_deadc0de_apple2ix_Apple2View_nativeRender(JNIEnv *env, jclass cls)
}
#endif
video_backend->render();
video_render();
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeReboot(JNIEnv *env, jobject obj) {
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeReboot(JNIEnv *env, jclass cls, jint resetState) {
LOG("...");
cpu65_reboot();
if (resetState) {
// joystick button settings should be balanced by joystick_reset() triggered on CPU thread
if (resetState == 1) {
run_args.joy_button0 = 0xff;
run_args.joy_button1 = 0x0;
} else {
run_args.joy_button0 = 0x0;
run_args.joy_button1 = 0xff;
}
}
cpu65_interrupt(ResetSig);
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnQuit(JNIEnv *env, jobject obj) {
#if TESTING
// test driver thread is managing CPU
#else
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnQuit(JNIEnv *env, jclass cls) {
appState = APP_REQUESTED_SHUTDOWN;
LOG("...");
disk6_eject(0);
disk6_eject(1);
#if DO_CPU65_TRACING
cpu65_trace_end();
#endif
cpu_resume();
#endif
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnKeyDown(JNIEnv *env, jobject obj, jint keyCode, jint metaState) {
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnKeyDown(JNIEnv *env, jclass cls, jint keyCode, jint metaState) {
if (UNLIKELY(appState != APP_RUNNING)) {
return;
}
android_keycode_to_emulator(keyCode, metaState, true);
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnKeyUp(JNIEnv *env, jobject obj, jint keyCode, jint metaState) {
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnKeyUp(JNIEnv *env, jclass cls, jint keyCode, jint metaState) {
if (UNLIKELY(appState != APP_RUNNING)) {
return;
}
android_keycode_to_emulator(keyCode, metaState, false);
}
jlong Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnTouch(JNIEnv *env, jobject obj, jint action, jint pointerCount, jint pointerIndex, jfloatArray xCoords, jfloatArray yCoords) {
void Java_org_deadc0de_apple2ix_Apple2View_nativeOnJoystickMove(JNIEnv *env, jclass cls, jint x, jint y) {
joydriver_setAxisValue((uint8_t)x, (uint8_t)y);
}
jlong Java_org_deadc0de_apple2ix_Apple2View_nativeOnTouch(JNIEnv *env, jclass cls, jint action, jint pointerCount, jint pointerIndex, jfloatArray xCoords, jfloatArray yCoords) {
//LOG(": %d/%d/%d :", action, pointerCount, pointerIndex);
SCOPE_TRACE_TOUCH("nativeOnTouch");
SCOPE_TRACE_INTERFACE("nativeOnTouch");
if (UNLIKELY(appState != APP_RUNNING)) {
return 0x0LL;
@@ -316,83 +358,306 @@ jlong Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnTouch(JNIEnv *env, jobje
return flags;
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeChooseDisk(JNIEnv *env, jobject obj, jstring jPath, jboolean driveA, jboolean readOnly) {
const char *path = (*env)->GetStringUTFChars(env, jPath, NULL);
int drive = driveA ? 0 : 1;
int ro = readOnly ? 1 : 0;
jstring Java_org_deadc0de_apple2ix_Apple2DisksMenu_nativeChooseDisk(JNIEnv *env, jclass cls, jstring jJsonString) {
#if TESTING
return NULL;
#endif
assert(cpu_isPaused() && "considered dangerous to insert disk image when CPU thread is running");
LOG(": (%s, %s, %s)", path, driveA ? "drive A" : "drive B", readOnly ? "read only" : "read/write");
if (disk6_insert(drive, path, ro)) {
char *gzPath = NULL;
asprintf(&gzPath, "%s.gz", path);
if (disk6_insert(drive, gzPath, ro)) {
char *diskImageUnreadable = "Disk Image Unreadable";
unsigned int cols = strlen(diskImageUnreadable);
video_backend->animation_showMessage(diskImageUnreadable, cols, 1);
} else {
video_backend->animation_showDiskChosen(drive);
const char *jsonString = (*env)->GetStringUTFChars(env, jJsonString, NULL);
JSON_ref jsonData = NULL;
bool ret = json_createFromString(jsonString, &jsonData);
assert(ret > 0);
(*env)->ReleaseStringUTFChars(env, jJsonString, jsonString); jsonString = NULL;
char *path = NULL;
json_mapCopyStringValue(jsonData, "disk", &path);
json_unescapeSlashes(&path);
assert(path != NULL);
assert(strlen(path) > 0);
bool readOnly = true;
json_mapParseBoolValue(jsonData, "readOnly", &readOnly);
long fd = -1;
if (!json_mapParseLongValue(jsonData, "fd", &fd, 10)) {
TEMP_FAILURE_RETRY(fd = open(path, readOnly ? O_RDONLY : O_RDWR));
if (fd == -1) {
LOG("OOPS could not open disk path : %s (%s)", path, strerror(errno));
}
FREE(gzPath);
} else {
video_backend->animation_showDiskChosen(drive);
TEMP_FAILURE_RETRY(fd = dup(fd));
if (fd == -1) {
LOG("OOPS could not dup file descriptor! (%s)", strerror(errno));
}
}
(*env)->ReleaseStringUTFChars(env, jPath, path);
long drive = -1;
json_mapParseLongValue(jsonData, "drive", &drive, 10);
assert(drive == 0 || drive == 1);
bool inserted = true;
const char *err = disk6_insert(fd, drive, path, readOnly);
if (err) {
char *diskImageUnreadable = "Disk Image Unreadable";
unsigned int cols = strlen(diskImageUnreadable);
video_getAnimationDriver()->animation_showMessage(diskImageUnreadable, cols, 1);
inserted = false;
} else {
video_getAnimationDriver()->animation_showDiskChosen(drive);
}
json_mapSetBoolValue(jsonData, "inserted", inserted);
if (fd >= 0) {
TEMP_FAILURE_RETRY(close(fd));
fd = -1;
}
if (path) {
FREE(path);
}
jsonString = ((JSON_s *)jsonData)->jsonString;
jstring jstr = (*env)->NewStringUTF(env, jsonString);
json_destroy(&jsonData);
LOG(": (fd:%d, %s, %s, %s)", (int)fd, path, drive ? "drive A" : "drive B", readOnly ? "read only" : "read/write");
return jstr;
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEjectDisk(JNIEnv *env, jobject obj, jboolean driveA) {
void Java_org_deadc0de_apple2ix_Apple2DisksMenu_nativeEjectDisk(JNIEnv *env, jclass cls, jboolean driveA) {
#if TESTING
return;
#endif
LOG("...");
disk6_eject(!driveA);
disk6_eject(driveA ? 0 : 1);
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeSaveState(JNIEnv *env, jobject obj, jstring jPath) {
const char *path = (*env)->GetStringUTFChars(env, jPath, NULL);
static void _openFdFromJson(OUTPARM int *fdOut, JSON_ref jsonData, const char * const fdKey, const char * const pathKey, int flags, int mode) {
assert(cpu_isPaused() && "considered dangerous to save state CPU thread is running");
long fd = -1;
char *path = NULL;
LOG(": (%s)", path);
if (!emulator_saveState(path)) {
do {
if (!json_mapParseLongValue(jsonData, fdKey, &fd, 10)) {
json_mapCopyStringValue(jsonData, pathKey, &path);
assert(path != NULL);
json_unescapeSlashes(&path);
if (strlen(path) <= 0) {
break;
}
if (mode == 0) {
TEMP_FAILURE_RETRY(fd = open(path, flags));
} else {
TEMP_FAILURE_RETRY(fd = open(path, flags, mode));
}
if (fd == -1) {
LOG("OOPS could not open state file path %s (%s)", path, strerror(errno));
}
} else {
TEMP_FAILURE_RETRY(fd = dup(fd));
if (fd == -1) {
LOG("OOPS could not dup file descriptor! (%s)", strerror(errno));
}
}
} while (0);
FREE(path);
*fdOut = (int)fd;
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeSaveState(JNIEnv *env, jclass cls, jstring jJsonString) {
assert(cpu_isPaused() && "considered dangerous to save state when CPU thread is running");
const char *jsonString = (*env)->GetStringUTFChars(env, jJsonString, NULL);
LOG(": (%s)", jsonString);
JSON_ref jsonData = NULL;
bool ret = json_createFromString(jsonString, &jsonData);
assert(ret > 0);
(*env)->ReleaseStringUTFChars(env, jJsonString, jsonString); jsonString = NULL;
int fdState = -1;
_openFdFromJson(&fdState, jsonData, /*fdKey:*/"fdState", /*pathKey:*/"stateFile", O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
if (!emulator_saveState(fdState)) {
LOG("OOPS, could not save emulator state");
}
(*env)->ReleaseStringUTFChars(env, jPath, path);
}
jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, jobject obj, jstring jPath) {
const char *path = (*env)->GetStringUTFChars(env, jPath, NULL);
assert(cpu_isPaused() && "considered dangerous to save state CPU thread is running");
LOG(": (%s)", path);
if (!emulator_loadState(path)) {
LOG("OOPS, could not load emulator state");
if (fdState >= 0) {
TEMP_FAILURE_RETRY(close(fdState));
fdState = -1;
}
(*env)->ReleaseStringUTFChars(env, jPath, path);
json_destroy(&jsonData);
}
// restoring state may cause a change in disk paths, so we need to notify the Java/Android menu system of the change
// (normally we drive state from the Java/menu side...)
char *disk1 = disk6.disk[0].file_name;
bool readOnly1 = disk6.disk[0].is_protected;
char *disk2 = disk6.disk[1].file_name;
bool readOnly2 = disk6.disk[1].is_protected;
char *str = NULL;
jstring jstr = NULL;
asprintf(&str, "{ disk1 = \"%s\"; readOnly1 = %s; disk2 = \"%s\"; readOnly2 = %s }", (disk1 ?: ""), readOnly1 ? "true" : "false", (disk2 ?: ""), readOnly2 ? "true" : "false");
if (str) {
jstr = (*env)->NewStringUTF(env, str);
FREE(str);
jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, jclass cls, jstring jJsonString) {
assert(cpu_isPaused() && "considered dangerous to load state when CPU thread is running");
const char *jsonString = (*env)->GetStringUTFChars(env, jJsonString, NULL);
LOG(": %s", jsonString);
JSON_ref jsonData = NULL;
int ret = json_createFromString(jsonString, &jsonData);
assert(ret > 0);
(*env)->ReleaseStringUTFChars(env, jJsonString, jsonString); jsonString = NULL;
int fdState = -1;
_openFdFromJson(&fdState, jsonData, /*fdKey:*/"fdState", /*pathKey:*/"stateFile", O_RDONLY, 0);
int fdA = -1;
{
bool readOnlyA = true;
json_mapParseBoolValue(jsonData, "readOnlyA", &readOnlyA);
_openFdFromJson(&fdA, jsonData, /*fdKey:*/"fdA", /*pathKey:*/"diskA", readOnlyA ? O_RDONLY : O_RDWR, 0);
}
int fdB = -1;
{
bool readOnlyB = true;
json_mapParseBoolValue(jsonData, "readOnlyB", &readOnlyB);
_openFdFromJson(&fdB, jsonData, /*fdKey:*/"fdB", /*pathKey:*/"diskB", readOnlyB ? O_RDONLY : O_RDWR, 0);
}
bool loadStateSuccess = true;
if (!emulator_loadState(fdState, (int)fdA, (int)fdB)) {
loadStateSuccess = false;
LOG("OOPS, could not load emulator state");
// FIXME TODO : should show invalid state animation here ...
}
if (fdState >= 0) {
TEMP_FAILURE_RETRY(close(fdState));
fdState = -1;
}
if (fdA >= 0) {
TEMP_FAILURE_RETRY(close(fdA));
fdA = -1;
}
if (fdB >= 0) {
TEMP_FAILURE_RETRY(close(fdB));
fdB = -1;
}
json_mapSetBoolValue(jsonData, "loadStateSuccess", loadStateSuccess);
jsonString = ((JSON_s *)jsonData)->jsonString;
jstring jstr = (*env)->NewStringUTF(env, jsonString);
json_destroy(&jsonData);
return jstr;
}
jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeStateExtractDiskPaths(JNIEnv *env, jclass cls, jstring jJsonString) {
assert(cpu_isPaused() && "considered dangerous to save state when CPU thread is running");
const char *jsonString = (*env)->GetStringUTFChars(env, jJsonString, NULL);
LOG(": (%s)", jsonString);
JSON_ref jsonData = NULL;
bool ret = json_createFromString(jsonString, &jsonData);
assert(ret > 0);
(*env)->ReleaseStringUTFChars(env, jJsonString, jsonString); jsonString = NULL;
int fdState = -1;
_openFdFromJson(&fdState, jsonData, /*fdKey:*/"fdState", /*pathKey:*/"stateFile", O_RDONLY, 0);
if (!emulator_stateExtractDiskPaths(fdState, jsonData)) {
LOG("OOPS, could not extract disk paths from emulator state file");
}
jsonString = ((JSON_s *)jsonData)->jsonString;
jstring jstr = (*env)->NewStringUTF(env, jsonString);
json_destroy(&jsonData);
if (fdState >= 0) {
TEMP_FAILURE_RETRY(close(fdState));
fdState = -1;
}
return jstr;
}
// ----------------------------------------------------------------------------
// Constructor
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativePrefsSync(JNIEnv *env, jclass cls, jstring jDomain) {
const char *domain = NULL;
__attribute__((constructor(CTOR_PRIORITY_LATE)))
static void _init_jnihooks(void) {
// ...
if (jDomain) {
domain = (*env)->GetStringUTFChars(env, jDomain, 0);
}
#if !TEST_PREFS
LOG("... domain: %s", domain);
prefs_load();
prefs_sync(domain);
#endif
if (jDomain) {
(*env)->ReleaseStringUTFChars(env, jDomain, domain);
}
}
jlong Java_org_deadc0de_apple2ix_Apple2JoystickCalibration_nativePollJoystick(JNIEnv *env, jclass cls) {
jlong cxy = 0;
long c = keys_consumeLastKey();
cxy |= (c << 16);
cxy |= ((joy_x & 0xFF) << 8);
cxy |= ((joy_y & 0xFF) << 0);
// last_ascii | last_scancode | joy_x | joy_y
return cxy;
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeLogMessage(JNIEnv *env, jclass cls, jstring jJsonString) {
#if TESTING
return NULL;
#endif
const char *jsonString = (*env)->GetStringUTFChars(env, jJsonString, NULL);
JSON_ref jsonData = NULL;
bool ret = json_createFromString(jsonString, &jsonData);
assert(ret > 0);
(*env)->ReleaseStringUTFChars(env, jJsonString, jsonString); jsonString = NULL;
long type = LOG_TYPE_INFO;
json_mapParseLongValue(jsonData, "type", &type, 10);
char *tag = NULL;
json_mapCopyStringValue(jsonData, "tag", &tag);
char *mesg = NULL;
json_mapCopyStringValue(jsonData, "mesg", &mesg);
log_taggedOutputString((log_type_t)type, tag, mesg);
if (tag) {
FREE(tag);
}
if (mesg) {
FREE(mesg);
}
json_destroy(&jsonData);
}

View File

@@ -1,316 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2015 Aaron Culliney
*
*/
#include "common.h"
#include <jni.h>
typedef enum AndroidTouchJoystickButtonValues {
//ANDROID_TOUCHJOY_NONE = 0,
ANDROID_TOUCHJOY_BUTTON0 = 1,
ANDROID_TOUCHJOY_BUTTON1,
ANDROID_TOUCHJOY_BUTTON_BOTH,
} AndroidTouchJoystickButtonValues;
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetColor(JNIEnv *env, jclass cls, jint color) {
LOG("color : %d", color);
#if TESTING
color_mode = COLOR;
#else
if (color < COLOR_NONE || color > COLOR_INTERP) {
return;
}
color_mode = color;
video_reset();
video_setpage(!!(softswitches & SS_SCREEN));
video_redraw();
#endif
}
jboolean Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetSpeakerEnabled(JNIEnv *env, jclass cls, jboolean enabled) {
LOG("enabled : %d", true);
// NO-OP ... speaker should always be enabled (but volume could be zero)
return true;
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetSpeakerVolume(JNIEnv *env, jclass cls, jint goesToTen) {
LOG("volume : %d", goesToTen);
assert(goesToTen >= 0);
sound_volume = goesToTen;
#warning FIXME TODO refactor/remove sound_volume ?
vm_reinitializeAudio();
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetAudioLatency(JNIEnv *env, jclass cls, jfloat latencySecs) {
#if !TESTING
LOG("audio latency : %fsecs", latencySecs);
assert(cpu_isPaused());
audio_setLatency(latencySecs);
timing_reinitializeAudio();
#endif
}
jboolean Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetMockingboardEnabled(JNIEnv *env, jclass cls, jboolean enabled) {
#if !TESTING
LOG("mockingboard enabled : %d", enabled);
assert(cpu_isPaused());
MB_SetEnabled(enabled);
timing_reinitializeAudio();
#endif
return enabled;
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetMockingboardVolume(JNIEnv *env, jclass cls, jint goesToTen) {
LOG("mockingboard volume : %d", goesToTen);
assert(goesToTen >= 0);
MB_SetVolumeZeroToTen(goesToTen);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetCurrentTouchDevice(JNIEnv *env, jclass cls, jint touchDevice) {
LOG("current touch device : %d", touchDevice);
assert(touchDevice >= 0 && touchDevice < TOUCH_DEVICE_DEVICE_MAX);
switch (touchDevice) {
case TOUCH_DEVICE_JOYSTICK:
keydriver_setTouchKeyboardOwnsScreen(false);
joydriver_setTouchJoystickOwnsScreen(true);
joydriver_setTouchVariant(EMULATED_JOYSTICK);
video_backend->animation_showTouchJoystick();
break;
case TOUCH_DEVICE_JOYSTICK_KEYPAD:
keydriver_setTouchKeyboardOwnsScreen(false);
joydriver_setTouchJoystickOwnsScreen(true);
joydriver_setTouchVariant(EMULATED_KEYPAD);
video_backend->animation_showTouchJoystick();
break;
case TOUCH_DEVICE_KEYBOARD:
keydriver_setTouchKeyboardOwnsScreen(true);
joydriver_setTouchJoystickOwnsScreen(false);
video_backend->animation_showTouchKeyboard();
break;
case TOUCH_DEVICE_NONE:
default:
break;
}
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickVisibility(JNIEnv *env, jclass cls, jboolean visibility) {
LOG("visibility: %d", visibility);
joydriver_setShowControls(visibility);
}
jint Java_org_deadc0de_apple2ix_Apple2Preferences_nativeGetCurrentTouchDevice(JNIEnv *env, jclass cls) {
LOG("%s", "");
if (joydriver_ownsScreen()) {
touchjoy_variant_t variant = joydriver_getTouchVariant();
if (variant == EMULATED_JOYSTICK) {
return TOUCH_DEVICE_JOYSTICK;
} else if (variant == EMULATED_KEYPAD) {
return TOUCH_DEVICE_JOYSTICK_KEYPAD;
}
} else if (keydriver_ownsScreen()) {
return TOUCH_DEVICE_KEYBOARD;
}
return TOUCH_DEVICE_NONE;
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchMenuEnabled(JNIEnv *env, jclass cls, jboolean enabled) {
LOG("enabled : %d", enabled);
interface_setTouchMenuEnabled(enabled);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetShowDiskOperationAnimation(JNIEnv *env, jclass cls, jboolean enabled) {
LOG("enabled : %d", enabled);
if (video_backend && video_backend->animation_setEnableShowTrackSector) {
video_backend->animation_setEnableShowTrackSector(enabled);
}
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchKeyboardLowercaseEnabled(JNIEnv *env, jclass cls, jboolean enabled) {
LOG("enabled : %d", enabled);
keydriver_setLowercaseEnabled(enabled);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchMenuVisibility(JNIEnv *env, jclass cls, jfloat alpha) {
LOG("visibility : %f", alpha);
interface_setTouchMenuVisibility(alpha);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchKeyboardVisibility(JNIEnv *env, jclass cls, jfloat inactiveAlpha, jfloat activeAlpha) {
LOG("inactive:%f active:%f", inactiveAlpha, activeAlpha);
keydriver_setVisibilityWhenOwnsScreen(inactiveAlpha, activeAlpha);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickButtonTypes(JNIEnv *env, jclass cls, jint touchDownButton, jint northButton, jint southButton) {
LOG(": %d,%d,%d", touchDownButton, northButton, southButton);
touchDownButton -= 1;
northButton -= 1;
southButton -= 1;
if (touchDownButton < TOUCH_NONE || touchDownButton > TOUCH_BOTH) {
ERRLOG("OOPS, invalid parameter!");
return;
}
if (northButton < TOUCH_NONE || northButton > TOUCH_BOTH) {
ERRLOG("OOPS, invalid parameter!");
return;
}
if (southButton < TOUCH_NONE || southButton > TOUCH_BOTH) {
ERRLOG("OOPS, invalid parameter!");
return;
}
uint8_t rosetteChars[ROSETTE_COLS * ROSETTE_ROWS];
int rosetteScancodes[ROSETTE_COLS * ROSETTE_ROWS];
rosetteChars[ROSETTE_NORTHWEST] = ' '; rosetteScancodes[ROSETTE_NORTHWEST] = -1;
rosetteChars[ROSETTE_NORTH] = (uint8_t)MOUSETEXT_UP; rosetteScancodes[ROSETTE_NORTH] = -1;
rosetteChars[ROSETTE_NORTHEAST] = ' '; rosetteScancodes[ROSETTE_NORTHEAST] = -1;
rosetteChars[ROSETTE_WEST] = (uint8_t)MOUSETEXT_LEFT; rosetteScancodes[ROSETTE_WEST] = -1;
rosetteChars[ROSETTE_CENTER] = '+'; rosetteScancodes[ROSETTE_CENTER] = -1;
rosetteChars[ROSETTE_EAST] = (uint8_t)MOUSETEXT_RIGHT; rosetteScancodes[ROSETTE_EAST] = -1;
rosetteChars[ROSETTE_SOUTHWEST] = ' '; rosetteScancodes[ROSETTE_SOUTHWEST] = -1;
rosetteChars[ROSETTE_SOUTH] = (uint8_t)MOUSETEXT_DOWN; rosetteScancodes[ROSETTE_SOUTH] = -1;
rosetteChars[ROSETTE_SOUTHEAST] = ' '; rosetteScancodes[ROSETTE_SOUTHEAST] = -1;
joydriver_setTouchAxisTypes(rosetteChars, rosetteScancodes);
joydriver_setTouchButtonTypes((touchjoy_button_type_t)touchDownButton, -1, (touchjoy_button_type_t)northButton, -1, (touchjoy_button_type_t)southButton, -1);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickTapDelay(JNIEnv *env, jclass cls, jfloat secs) {
LOG("tap delay : %f", secs);
joydriver_setTapDelay(secs);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickAxisSensitivity(JNIEnv *env, jclass cls, jfloat multiplier) {
LOG("axis sensitivity : %f", multiplier);
joydriver_setTouchAxisSensitivity(multiplier);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickButtonSwitchThreshold(JNIEnv *env, jclass cls, jint delta) {
LOG("delta : %d", delta);
joydriver_setButtonSwitchThreshold(delta);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetScreenDivision(JNIEnv *env, jclass cls, jfloat division) {
LOG("division : %f", division);
joydriver_setScreenDivision(division);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetAxisOnLeft(JNIEnv *env, jclass cls, jboolean axisIsOnLeft) {
LOG("axis on left : %d", axisIsOnLeft);
joydriver_setAxisOnLeft(axisIsOnLeft);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetKeypadTypes(JNIEnv *env, jclass cls,
jintArray jRosetteChars, jintArray jRosetteScans, jintArray jButtonsChars, jintArray jButtonsScans)
{
jint *rosetteChars = (*env)->GetIntArrayElements(env, jRosetteChars, 0);
jint *rosetteScans = (*env)->GetIntArrayElements(env, jRosetteScans, 0);
jint *buttonsChars = (*env)->GetIntArrayElements(env, jButtonsChars, 0);
jint *buttonsScans = (*env)->GetIntArrayElements(env, jButtonsScans, 0);
LOG("NW:%c/%d, N:%c/%d, NE:%c/%d, ... SWIPEUP:%c/%d",
(char)rosetteChars[0], rosetteScans[0], (char)rosetteChars[1], rosetteScans[1], (char)rosetteChars[2], rosetteScans[2],
(char)buttonsChars[1], buttonsScans[1]);
LOG(" W:%c/%d, C:%c/%d, E:%c/%d, ... TAP:%c/%d",
(char)rosetteChars[3], rosetteScans[3], (char)rosetteChars[4], rosetteScans[4], (char)rosetteChars[5], rosetteScans[5],
(char)buttonsChars[0], buttonsScans[0]);
LOG("SW:%c/%d, S:%c/%d, SE:%c/%d, ... SWIPEDN:%c/%d",
(char)rosetteChars[6], rosetteScans[6], (char)rosetteChars[7], rosetteScans[7], (char)rosetteChars[8], rosetteScans[8],
(char)buttonsChars[2], buttonsScans[2]);
// we could just pass these as jcharArray ... but this isn't a tight loop =P
uint8_t actualChars[ROSETTE_ROWS * ROSETTE_COLS];
for (unsigned int i=0; i<(ROSETTE_ROWS * ROSETTE_COLS); i++) {
actualChars[i] = (uint8_t)rosetteChars[i];
}
joydriver_setTouchAxisTypes(actualChars, rosetteScans);
joydriver_setTouchButtonTypes(
(touchjoy_button_type_t)buttonsChars[0], buttonsScans[0],
(touchjoy_button_type_t)buttonsChars[1], buttonsScans[1],
(touchjoy_button_type_t)buttonsChars[2], buttonsScans[2]);
(*env)->ReleaseIntArrayElements(env, jRosetteChars, rosetteChars, 0);
(*env)->ReleaseIntArrayElements(env, jRosetteScans, rosetteScans, 0);
(*env)->ReleaseIntArrayElements(env, jButtonsChars, buttonsChars, 0);
(*env)->ReleaseIntArrayElements(env, jButtonsScans, buttonsScans, 0);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchDeviceBeginCalibrationMode(JNIEnv *env, jclass cls) {
LOG("%s", "");
if (joydriver_ownsScreen()) {
joydriver_beginCalibration();
} else if (keydriver_ownsScreen()) {
keydriver_beginCalibration();
}
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchDeviceEndCalibrationMode(JNIEnv *env, jclass cls) {
LOG("%s", "");
if (joydriver_ownsScreen()) {
joydriver_endCalibration();
} else if (keydriver_ownsScreen()) {
keydriver_endCalibration();
}
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchDeviceKeyRepeatThreshold(JNIEnv *env, jclass cls, jfloat threshold) {
LOG("threshold : %f", threshold);
joydriver_setKeyRepeatThreshold(threshold);
}
jint Java_org_deadc0de_apple2ix_Apple2Preferences_nativeGetCPUSpeed(JNIEnv *env, jclass cls) {
LOG("%s", "");
return (jint)round(cpu_scale_factor * 100.0);
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetCPUSpeed(JNIEnv *env, jclass cls, jint percentSpeed) {
LOG("percentSpeed : %d%%", percentSpeed);
#if TESTING
cpu_scale_factor = CPU_SCALE_FASTEST;
cpu_altscale_factor = CPU_SCALE_FASTEST;
timing_initialize();
#else
bool wasPaused = cpu_isPaused();
if (!wasPaused) {
cpu_pause();
}
cpu_scale_factor = percentSpeed/100.0;
if (cpu_scale_factor > CPU_SCALE_FASTEST) {
cpu_scale_factor = CPU_SCALE_FASTEST;
}
if (cpu_scale_factor < CPU_SCALE_SLOWEST) {
cpu_scale_factor = CPU_SCALE_SLOWEST;
}
if (video_backend->animation_showCPUSpeed) {
video_backend->animation_showCPUSpeed();
}
timing_initialize();
if (!wasPaused) {
cpu_resume();
}
#endif
}
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeLoadTouchKeyboardJSON(JNIEnv *env, jclass cls, jstring j_jsonPath) {
const char *jsonPath = (*env)->GetStringUTFChars(env, j_jsonPath, 0);
LOG("jsonPath: %s", jsonPath);
keydriver_loadAltKbd(jsonPath);
(*env)->ReleaseStringUTFChars(env, j_jsonPath, jsonPath);
}

View File

@@ -4,44 +4,68 @@
APPLE2_SRC_PATH := apple2ix-src
APPLE2_X86_SRC := \
$(APPLE2_SRC_PATH)/x86/glue.S $(APPLE2_SRC_PATH)/x86/cpu.S
$(APPLE2_SRC_PATH)/x86/cpu.S \
$(APPLE2_SRC_PATH)/x86/glue.S
APPLE2_ARM_SRC := \
$(APPLE2_SRC_PATH)/arm/glue.S $(APPLE2_SRC_PATH)/arm/cpu.S
$(APPLE2_SRC_PATH)/arm/cpu.S \
$(APPLE2_SRC_PATH)/arm/glue.S
APPLE2_VIDEO_SRC = \
$(APPLE2_SRC_PATH)/video/glvideo.c \
$(APPLE2_SRC_PATH)/video/glnode.c \
$(APPLE2_SRC_PATH)/video/glhudmodel.c \
$(APPLE2_SRC_PATH)/video/glalert.c \
$(APPLE2_SRC_PATH)/video/glhudmodel.c \
$(APPLE2_SRC_PATH)/video/glnode.c \
$(APPLE2_SRC_PATH)/video/gltouchjoy.c \
$(APPLE2_SRC_PATH)/video/gltouchjoy_joy.c \
$(APPLE2_SRC_PATH)/video/gltouchjoy_kpad.c \
$(APPLE2_SRC_PATH)/video/gltouchkbd.c \
$(APPLE2_SRC_PATH)/video/gltouchmenu.c \
$(APPLE2_SRC_PATH)/video/glvideo.c \
$(APPLE2_SRC_PATH)/video/ntsc.c \
$(APPLE2_SRC_PATH)/video/video.c \
$(APPLE2_SRC_PATH)/video_util/matrixUtil.c \
$(APPLE2_SRC_PATH)/video_util/modelUtil.c \
$(APPLE2_SRC_PATH)/video_util/sourceUtil.c \
$(APPLE2_SRC_PATH)/video_util/vectorUtil.c
APPLE2_AUDIO_SRC = \
$(APPLE2_SRC_PATH)/audio/soundcore.c $(APPLE2_SRC_PATH)/audio/soundcore-opensles.c $(APPLE2_SRC_PATH)/audio/speaker.c \
$(APPLE2_SRC_PATH)/audio/mockingboard.c $(APPLE2_SRC_PATH)/audio/AY8910.c
$(APPLE2_SRC_PATH)/audio/AY8910.c \
$(APPLE2_SRC_PATH)/audio/mockingboard.c \
$(APPLE2_SRC_PATH)/audio/soundcore.c \
$(APPLE2_SRC_PATH)/audio/soundcore-opensles.c \
$(APPLE2_SRC_PATH)/audio/speaker.c
APPLE2_META_SRC = \
$(APPLE2_SRC_PATH)/meta/debug.c $(APPLE2_SRC_PATH)/meta/debugger.c $(APPLE2_SRC_PATH)/meta/opcodes.c \
$(APPLE2_SRC_PATH)/meta/lintrace.c $(APPLE2_SRC_PATH)/test/sha1.c $(APPLE2_SRC_PATH)/json_parse.c \
$(APPLE2_SRC_PATH)/../externals/jsmn/jsmn.c
$(APPLE2_SRC_PATH)/meta/debug.c \
$(APPLE2_SRC_PATH)/meta/debugger.c \
$(APPLE2_SRC_PATH)/meta/systrace.c \
$(APPLE2_SRC_PATH)/meta/log.c \
$(APPLE2_SRC_PATH)/meta/memmngt.c \
$(APPLE2_SRC_PATH)/meta/opcodes.c \
$(APPLE2_SRC_PATH)/test/sha1.c \
APPLE2_MAIN_SRC = \
$(APPLE2_SRC_PATH)/font.c $(APPLE2_SRC_PATH)/rom.c $(APPLE2_SRC_PATH)/misc.c $(APPLE2_SRC_PATH)/display.c $(APPLE2_SRC_PATH)/vm.c \
$(APPLE2_SRC_PATH)/timing.c $(APPLE2_SRC_PATH)/zlib-helpers.c $(APPLE2_SRC_PATH)/joystick.c $(APPLE2_SRC_PATH)/keys.c \
$(APPLE2_SRC_PATH)/interface.c $(APPLE2_SRC_PATH)/disk.c $(APPLE2_SRC_PATH)/cpu-supp.c \
jnihooks.c jniprefs.c androidkeys.c
androidkeys.c \
jnihooks.c \
$(APPLE2_SRC_PATH)/cpu-supp.c \
$(APPLE2_SRC_PATH)/disk.c \
$(APPLE2_SRC_PATH)/display.c \
$(APPLE2_SRC_PATH)/font.c \
$(APPLE2_SRC_PATH)/interface.c \
$(APPLE2_SRC_PATH)/joystick.c \
$(APPLE2_SRC_PATH)/json_parse.c \
$(APPLE2_SRC_PATH)/keys.c \
$(APPLE2_SRC_PATH)/misc.c \
$(APPLE2_SRC_PATH)/prefs.c \
$(APPLE2_SRC_PATH)/rom.c \
$(APPLE2_SRC_PATH)/timing.c \
$(APPLE2_SRC_PATH)/vm.c \
$(APPLE2_SRC_PATH)/zlib-helpers.c \
$(APPLE2_SRC_PATH)/../externals/jsmn/jsmn.c
APPLE2_OPTIM_CFLAGS := -g -O2
APPLE2_BASE_CFLAGS := -DAPPLE2IX=1 -DINTERFACE_TOUCH=1 -DMOBILE_DEVICE=1 -DVIDEO_OPENGL=1 -DDEBUGGER=1 -DAUDIO_ENABLED=1 -std=gnu11 -DPREVENT_TEXTREL=1 -fPIC $(APPLE2_OPTIM_CFLAGS) -I$(APPLE2_SRC_PATH)
APPLE2_BASE_LDLIBS := -llog -landroid -lGLESv2 -lz -lOpenSLES
APPLE2_OPTIM_CFLAGS := -O2 # match the same optimization level as BUILD_MODE=release for ndk-build
APPLE2_BASE_CFLAGS := -Wall -DAPPLE2IX=1 -DINTERFACE_TOUCH=1 -DMOBILE_DEVICE=1 -DVIDEO_OPENGL=1 -std=gnu11 -fPIC $(APPLE2_OPTIM_CFLAGS) -I$(APPLE2_SRC_PATH)
APPLE2_BASE_LDLIBS := -Wl,-z,text -Wl,-z,noexecstack -llog -landroid -lGLESv2 -lz -lOpenSLES -latomic
LOCAL_WHOLE_STATIC_LIBRARIES += cpufeatures

View File

@@ -0,0 +1,6 @@
#!/system/bin/sh
# WARNING : these $ variables need to be defined above and outside this bundled script
set -x
PACKAGE=org.deadc0de.apple2ix.basic
export TMPDIR=/data/data/org.deadc0de.apple2ix.basic
exec /data/local/Inst/bin/valgrind --gen-suppressions=all $GPU_VARIANT --num-callers=16 --error-limit=no -v --error-limit=no --default-suppressions=yes --suppressions=/data/local/Inst/lib/valgrind/default.supp --trace-children=yes --log-file=/sdcard/valgrind.log.%p --tool=memcheck --leak-check=full --show-reachable=yes $*

View File

@@ -12,16 +12,23 @@ include $(COMMON_SOURCES_MK)
LOCAL_MODULE := libapple2ix
LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/test/testcommon.c $(APPLE2_SRC_PATH)/test/testcpu.c
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -DTEST_CPU -DTESTING=1
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -g -DTEST_CPU=1 -DTESTING=1 -I$(APPLE2_SRC_PATH)/test
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
LOCAL_CFLAGS += -DNO_UNDERSCORES=1
else
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
endif
ifeq ($(BUILD_MODE),release)
LOCAL_CFLAGS += -DNDEBUG=1
else
LOCAL_CFLAGS += -g
endif
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
# Build a shared library and let Java/Dalvik drive

View File

@@ -12,16 +12,23 @@ include $(COMMON_SOURCES_MK)
LOCAL_MODULE := libapple2ix
LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/test/testcommon.c $(APPLE2_SRC_PATH)/test/testdisk.c
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -DTEST_DISK -DTESTING=1 -DDISK_TRACING=1
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -g -DTEST_DISK=1 -DTESTING=1 -DDISK_TRACING=1 -I$(APPLE2_SRC_PATH)/test
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
LOCAL_CFLAGS += -DNO_UNDERSCORES=1
else
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
endif
ifeq ($(BUILD_MODE),release)
LOCAL_CFLAGS += -DNDEBUG=1
else
LOCAL_CFLAGS += -g
endif
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
# Build a shared library and let Java/Dalvik drive

View File

@@ -12,16 +12,23 @@ include $(COMMON_SOURCES_MK)
LOCAL_MODULE := libapple2ix
LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/test/testcommon.c $(APPLE2_SRC_PATH)/test/testdisplay.c
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -DTEST_DISPLAY -DTESTING=1
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -g -DTEST_DISPLAY=1 -DTESTING=1 -I$(APPLE2_SRC_PATH)/test
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
LOCAL_CFLAGS += -DNO_UNDERSCORES=1
else
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
endif
ifeq ($(BUILD_MODE),release)
LOCAL_CFLAGS += -DNDEBUG=1
else
LOCAL_CFLAGS += -g
endif
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
# Build a shared library and let Java/Dalvik drive

1
Android/jni/testprefs Symbolic link
View File

@@ -0,0 +1 @@
build.sh

40
Android/jni/testprefs.mk Normal file
View File

@@ -0,0 +1,40 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
PACKAGE_IDENTIFIER := "org.deadc0de.apple2ix"
PACKAGE_NAME := "apple2ix"
COMMON_SOURCES_MK := $(LOCAL_PATH)/sources.mk
include $(COMMON_SOURCES_MK)
# -----------------------------------------------------------------------------
# Android build config
LOCAL_MODULE := libapple2ix
LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/test/testcommon.c $(APPLE2_SRC_PATH)/test/testprefs.c
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -g -DTEST_PREFS=1 -DTESTING=1 -I$(APPLE2_SRC_PATH)/test
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
LOCAL_CFLAGS += -DNO_UNDERSCORES=1
else
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
endif
ifeq ($(BUILD_MODE),release)
LOCAL_CFLAGS += -DNDEBUG=1
else
LOCAL_CFLAGS += -g
endif
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
# Build a shared library and let Java/Dalvik drive
include $(BUILD_SHARED_LIBRARY)
# --OR-- Build an executable so native can drive this show
#include $(BUILD_EXECUTABLE)
$(call import-module, android/cpufeatures)

1
Android/jni/testtrace Symbolic link
View File

@@ -0,0 +1 @@
build.sh

40
Android/jni/testtrace.mk Normal file
View File

@@ -0,0 +1,40 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
PACKAGE_IDENTIFIER := "org.deadc0de.apple2ix"
PACKAGE_NAME := "apple2ix"
COMMON_SOURCES_MK := $(LOCAL_PATH)/sources.mk
include $(COMMON_SOURCES_MK)
# -----------------------------------------------------------------------------
# Android build config
LOCAL_MODULE := libapple2ix
LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/test/testcommon.c $(APPLE2_SRC_PATH)/test/testtrace.c
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -g -DTEST_TRACE=1 -DTESTING=1 -DCPU_TRACING=1 -DDISK_TRACING=1 -DSPEAKER_TRACING=1 -DMB_TRACING=1 -I$(APPLE2_SRC_PATH)/test
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
LOCAL_CFLAGS += -DNO_UNDERSCORES=1
else
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
endif
ifeq ($(BUILD_MODE),release)
LOCAL_CFLAGS += -DNDEBUG=1
else
LOCAL_CFLAGS += -g
endif
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
# Build a shared library and let Java/Dalvik drive
include $(BUILD_SHARED_LIBRARY)
# --OR-- Build an executable so native can drive this show
#include $(BUILD_EXECUTABLE)
$(call import-module, android/cpufeatures)

1
Android/jni/testui Symbolic link
View File

@@ -0,0 +1 @@
build.sh

40
Android/jni/testui.mk Normal file
View File

@@ -0,0 +1,40 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
PACKAGE_IDENTIFIER := "org.deadc0de.apple2ix"
PACKAGE_NAME := "apple2ix"
COMMON_SOURCES_MK := $(LOCAL_PATH)/sources.mk
include $(COMMON_SOURCES_MK)
# -----------------------------------------------------------------------------
# Android build config
LOCAL_MODULE := libapple2ix
LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/test/testcommon.c $(APPLE2_SRC_PATH)/test/testui.c
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -g -DTEST_UI=1 -DTESTING=1 -I$(APPLE2_SRC_PATH)/test
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
LOCAL_CFLAGS += -DNO_UNDERSCORES=1
else
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
endif
ifeq ($(BUILD_MODE),release)
LOCAL_CFLAGS += -DNDEBUG=1
else
LOCAL_CFLAGS += -g
endif
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
# Build a shared library and let Java/Dalvik drive
include $(BUILD_SHARED_LIBRARY)
# --OR-- Build an executable so native can drive this show
#include $(BUILD_EXECUTABLE)
$(call import-module, android/cpufeatures)

View File

@@ -12,16 +12,23 @@ include $(COMMON_SOURCES_MK)
LOCAL_MODULE := libapple2ix
LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/test/testcommon.c $(APPLE2_SRC_PATH)/test/testvm.c
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -DTEST_VM -DTESTING=1
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -g -DTEST_VM=1 -DTESTING=1 -I$(APPLE2_SRC_PATH)/test
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
LOCAL_CFLAGS += -DNO_UNDERSCORES=1
else
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
endif
ifeq ($(BUILD_MODE),release)
LOCAL_CFLAGS += -DNDEBUG=1
else
LOCAL_CFLAGS += -g
endif
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
# Build a shared library and let Java/Dalvik drive

View File

@@ -0,0 +1,516 @@
#
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import atexit
import base64
import logging
import os
import re
import subprocess
class FindDeviceError(RuntimeError):
pass
class DeviceNotFoundError(FindDeviceError):
def __init__(self, serial):
self.serial = serial
super(DeviceNotFoundError, self).__init__(
'No device with serial {}'.format(serial))
class NoUniqueDeviceError(FindDeviceError):
def __init__(self):
super(NoUniqueDeviceError, self).__init__('No unique device')
class ShellError(RuntimeError):
def __init__(self, cmd, stdout, stderr, exit_code):
super(ShellError, self).__init__(
'`{0}` exited with code {1}'.format(cmd, exit_code))
self.cmd = cmd
self.stdout = stdout
self.stderr = stderr
self.exit_code = exit_code
def get_devices(adb_path='adb'):
with open(os.devnull, 'wb') as devnull:
subprocess.check_call([adb_path, 'start-server'], stdout=devnull,
stderr=devnull)
out = split_lines(subprocess.check_output([adb_path, 'devices']))
# The first line of `adb devices` just says "List of attached devices", so
# skip that.
devices = []
for line in out[1:]:
if not line.strip():
continue
if 'offline' in line:
continue
serial, _ = re.split(r'\s+', line, maxsplit=1)
devices.append(serial)
return devices
def _get_unique_device(product=None, adb_path='adb'):
devices = get_devices(adb_path=adb_path)
if len(devices) != 1:
raise NoUniqueDeviceError()
return AndroidDevice(devices[0], product, adb_path)
def _get_device_by_serial(serial, product=None, adb_path='adb'):
for device in get_devices(adb_path=adb_path):
if device == serial:
return AndroidDevice(serial, product, adb_path)
raise DeviceNotFoundError(serial)
def get_device(serial=None, product=None, adb_path='adb'):
"""Get a uniquely identified AndroidDevice if one is available.
Raises:
DeviceNotFoundError:
The serial specified by `serial` or $ANDROID_SERIAL is not
connected.
NoUniqueDeviceError:
Neither `serial` nor $ANDROID_SERIAL was set, and the number of
devices connected to the system is not 1. Having 0 connected
devices will also result in this error.
Returns:
An AndroidDevice associated with the first non-None identifier in the
following order of preference:
1) The `serial` argument.
2) The environment variable $ANDROID_SERIAL.
3) The single device connnected to the system.
"""
if serial is not None:
return _get_device_by_serial(serial, product, adb_path)
android_serial = os.getenv('ANDROID_SERIAL')
if android_serial is not None:
return _get_device_by_serial(android_serial, product, adb_path)
return _get_unique_device(product, adb_path=adb_path)
def _get_device_by_type(flag, adb_path):
with open(os.devnull, 'wb') as devnull:
subprocess.check_call([adb_path, 'start-server'], stdout=devnull,
stderr=devnull)
try:
serial = subprocess.check_output(
[adb_path, flag, 'get-serialno']).strip()
except subprocess.CalledProcessError:
raise RuntimeError('adb unexpectedly returned nonzero')
if serial == 'unknown':
raise NoUniqueDeviceError()
return _get_device_by_serial(serial, adb_path=adb_path)
def get_usb_device(adb_path='adb'):
"""Get the unique USB-connected AndroidDevice if it is available.
Raises:
NoUniqueDeviceError:
0 or multiple devices are connected via USB.
Returns:
An AndroidDevice associated with the unique USB-connected device.
"""
return _get_device_by_type('-d', adb_path=adb_path)
def get_emulator_device(adb_path='adb'):
"""Get the unique emulator AndroidDevice if it is available.
Raises:
NoUniqueDeviceError:
0 or multiple emulators are running.
Returns:
An AndroidDevice associated with the unique running emulator.
"""
return _get_device_by_type('-e', adb_path=adb_path)
# If necessary, modifies subprocess.check_output() or subprocess.Popen() args
# to run the subprocess via Windows PowerShell to work-around an issue in
# Python 2's subprocess class on Windows where it doesn't support Unicode.
def _get_subprocess_args(args):
# Only do this slow work-around if Unicode is in the cmd line on Windows.
# PowerShell takes 600-700ms to startup on a 2013-2014 machine, which is
# very slow.
if os.name != 'nt' or all(not isinstance(arg, unicode) for arg in args[0]):
return args
def escape_arg(arg):
# Escape for the parsing that the C Runtime does in Windows apps. In
# particular, this will take care of double-quotes.
arg = subprocess.list2cmdline([arg])
# Escape single-quote with another single-quote because we're about
# to...
arg = arg.replace(u"'", u"''")
# ...put the arg in a single-quoted string for PowerShell to parse.
arg = u"'" + arg + u"'"
return arg
# Escape command line args.
argv = map(escape_arg, args[0])
# Cause script errors (such as adb not found) to stop script immediately
# with an error.
ps_code = u'$ErrorActionPreference = "Stop"\r\n'
# Add current directory to the PATH var, to match cmd.exe/CreateProcess()
# behavior.
ps_code += u'$env:Path = ".;" + $env:Path\r\n'
# Precede by &, the PowerShell call operator, and separate args by space.
ps_code += u'& ' + u' '.join(argv)
# Make the PowerShell exit code the exit code of the subprocess.
ps_code += u'\r\nExit $LastExitCode'
# Encode as UTF-16LE (without Byte-Order-Mark) which Windows natively
# understands.
ps_code = ps_code.encode('utf-16le')
# Encode the PowerShell command as base64 and use the special
# -EncodedCommand option that base64 decodes. Base64 is just plain ASCII,
# so it should have no problem passing through Win32 CreateProcessA()
# (which python erroneously calls instead of CreateProcessW()).
return (['powershell.exe', '-NoProfile', '-NonInteractive',
'-EncodedCommand', base64.b64encode(ps_code)],) + args[1:]
# Call this instead of subprocess.check_output() to work-around issue in Python
# 2's subprocess class on Windows where it doesn't support Unicode.
def _subprocess_check_output(*args, **kwargs):
try:
return subprocess.check_output(*_get_subprocess_args(args), **kwargs)
except subprocess.CalledProcessError as e:
# Show real command line instead of the powershell.exe command line.
raise subprocess.CalledProcessError(e.returncode, args[0],
output=e.output)
# Call this instead of subprocess.Popen(). Like _subprocess_check_output().
def _subprocess_Popen(*args, **kwargs):
return subprocess.Popen(*_get_subprocess_args(args), **kwargs)
def split_lines(s):
"""Splits lines in a way that works even on Windows and old devices.
Windows will see \r\n instead of \n, old devices do the same, old devices
on Windows will see \r\r\n.
"""
# rstrip is used here to workaround a difference between splineslines and
# re.split:
# >>> 'foo\n'.splitlines()
# ['foo']
# >>> re.split(r'\n', 'foo\n')
# ['foo', '']
return re.split(r'[\r\n]+', s.rstrip())
def version(adb_path=None):
"""Get the version of adb (in terms of ADB_SERVER_VERSION)."""
adb_path = adb_path if adb_path is not None else ['adb']
version_output = subprocess.check_output(adb_path + ['version'])
pattern = r'^Android Debug Bridge version 1.0.(\d+)$'
result = re.match(pattern, version_output.splitlines()[0])
if not result:
return 0
return int(result.group(1))
class AndroidDevice(object):
# Delimiter string to indicate the start of the exit code.
_RETURN_CODE_DELIMITER = 'x'
# Follow any shell command with this string to get the exit
# status of a program since this isn't propagated by adb.
#
# The delimiter is needed because `printf 1; echo $?` would print
# "10", and we wouldn't be able to distinguish the exit code.
_RETURN_CODE_PROBE = [';', 'echo', '{0}$?'.format(_RETURN_CODE_DELIMITER)]
# Maximum search distance from the output end to find the delimiter.
# adb on Windows returns \r\n even if adbd returns \n. Some old devices
# seem to actually return \r\r\n.
_RETURN_CODE_SEARCH_LENGTH = len(
'{0}255\r\r\n'.format(_RETURN_CODE_DELIMITER))
def __init__(self, serial, product=None, adb_path='adb'):
self.serial = serial
self.product = product
self.adb_cmd = [adb_path]
if self.serial is not None:
self.adb_cmd.extend(['-s', serial])
if self.product is not None:
self.adb_cmd.extend(['-p', product])
self._linesep = None
self._features = None
@property
def linesep(self):
if self._linesep is None:
self._linesep = subprocess.check_output(self.adb_cmd +
['shell', 'echo'])
return self._linesep
@property
def features(self):
if self._features is None:
try:
self._features = split_lines(self._simple_call(['features']))
except subprocess.CalledProcessError:
self._features = []
return self._features
def has_shell_protocol(self):
return version(self.adb_cmd) >= 35 and 'shell_v2' in self.features
def _make_shell_cmd(self, user_cmd):
command = self.adb_cmd + ['shell'] + user_cmd
if not self.has_shell_protocol():
command += self._RETURN_CODE_PROBE
return command
def _parse_shell_output(self, out):
"""Finds the exit code string from shell output.
Args:
out: Shell output string.
Returns:
An (exit_code, output_string) tuple. The output string is
cleaned of any additional stuff we appended to find the
exit code.
Raises:
RuntimeError: Could not find the exit code in |out|.
"""
search_text = out
if len(search_text) > self._RETURN_CODE_SEARCH_LENGTH:
# We don't want to search over massive amounts of data when we know
# the part we want is right at the end.
search_text = search_text[-self._RETURN_CODE_SEARCH_LENGTH:]
partition = search_text.rpartition(self._RETURN_CODE_DELIMITER)
if partition[1] == '':
raise RuntimeError('Could not find exit status in shell output.')
result = int(partition[2])
# partition[0] won't contain the full text if search_text was
# truncated, pull from the original string instead.
out = out[:-len(partition[1]) - len(partition[2])]
return result, out
def _simple_call(self, cmd):
logging.info(' '.join(self.adb_cmd + cmd))
return _subprocess_check_output(
self.adb_cmd + cmd, stderr=subprocess.STDOUT)
def shell(self, cmd):
"""Calls `adb shell`
Args:
cmd: command to execute as a list of strings.
Returns:
A (stdout, stderr) tuple. Stderr may be combined into stdout
if the device doesn't support separate streams.
Raises:
ShellError: the exit code was non-zero.
"""
exit_code, stdout, stderr = self.shell_nocheck(cmd)
if exit_code != 0:
raise ShellError(cmd, stdout, stderr, exit_code)
return stdout, stderr
def shell_nocheck(self, cmd):
"""Calls `adb shell`
Args:
cmd: command to execute as a list of strings.
Returns:
An (exit_code, stdout, stderr) tuple. Stderr may be combined
into stdout if the device doesn't support separate streams.
"""
cmd = self._make_shell_cmd(cmd)
logging.info(' '.join(cmd))
p = _subprocess_Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if self.has_shell_protocol():
exit_code = p.returncode
else:
exit_code, stdout = self._parse_shell_output(stdout)
return exit_code, stdout, stderr
def shell_popen(self, cmd, kill_atexit=True, preexec_fn=None,
creationflags=0, **kwargs):
"""Calls `adb shell` and returns a handle to the adb process.
This function provides direct access to the subprocess used to run the
command, without special return code handling. Users that need the
return value must retrieve it themselves.
Args:
cmd: Array of command arguments to execute.
kill_atexit: Whether to kill the process upon exiting.
preexec_fn: Argument forwarded to subprocess.Popen.
creationflags: Argument forwarded to subprocess.Popen.
**kwargs: Arguments forwarded to subprocess.Popen.
Returns:
subprocess.Popen handle to the adb shell instance
"""
command = self.adb_cmd + ['shell'] + cmd
# Make sure a ctrl-c in the parent script doesn't kill gdbserver.
if os.name == 'nt':
creationflags |= subprocess.CREATE_NEW_PROCESS_GROUP
else:
if preexec_fn is None:
preexec_fn = os.setpgrp
elif preexec_fn is not os.setpgrp:
fn = preexec_fn
def _wrapper():
fn()
os.setpgrp()
preexec_fn = _wrapper
p = _subprocess_Popen(command, creationflags=creationflags,
preexec_fn=preexec_fn, **kwargs)
if kill_atexit:
atexit.register(p.kill)
return p
def install(self, filename, replace=False):
cmd = ['install']
if replace:
cmd.append('-r')
cmd.append(filename)
return self._simple_call(cmd)
def push(self, local, remote):
return self._simple_call(['push', local, remote])
def pull(self, remote, local):
return self._simple_call(['pull', remote, local])
def sync(self, directory=None):
cmd = ['sync']
if directory is not None:
cmd.append(directory)
return self._simple_call(cmd)
def tcpip(self, port):
return self._simple_call(['tcpip', port])
def usb(self):
return self._simple_call(['usb'])
def reboot(self):
return self._simple_call(['reboot'])
def remount(self):
return self._simple_call(['remount'])
def root(self):
return self._simple_call(['root'])
def unroot(self):
return self._simple_call(['unroot'])
def connect(self, host):
return self._simple_call(['connect', host])
def disconnect(self, host):
return self._simple_call(['disconnect', host])
def forward(self, local, remote):
return self._simple_call(['forward', local, remote])
def forward_list(self):
return self._simple_call(['forward', '--list'])
def forward_no_rebind(self, local, remote):
return self._simple_call(['forward', '--no-rebind', local, remote])
def forward_remove(self, local):
return self._simple_call(['forward', '--remove', local])
def forward_remove_all(self):
return self._simple_call(['forward', '--remove-all'])
def reverse(self, remote, local):
return self._simple_call(['reverse', remote, local])
def reverse_list(self):
return self._simple_call(['reverse', '--list'])
def reverse_no_rebind(self, local, remote):
return self._simple_call(['reverse', '--no-rebind', local, remote])
def reverse_remove_all(self):
return self._simple_call(['reverse', '--remove-all'])
def reverse_remove(self, remote):
return self._simple_call(['reverse', '--remove', remote])
def wait(self):
return self._simple_call(['wait-for-device'])
def get_props(self):
result = {}
output, _ = self.shell(['getprop'])
output = split_lines(output)
pattern = re.compile(r'^\[([^]]+)\]: \[(.*)\]')
for line in output:
match = pattern.match(line)
if match is None:
# apple2ix NOTE : don't freak out here ...
#raise RuntimeError('invalid getprop line: "{}"'.format(line))
continue
key = match.group(1)
value = match.group(2)
if key in result:
raise RuntimeError('duplicate getprop key: "{}"'.format(key))
result[key] = value
return result
def get_prop(self, prop_name):
output = split_lines(self.shell(['getprop', prop_name])[0])
if len(output) != 1:
raise RuntimeError('Too many lines in getprop output:\n' +
'\n'.join(output))
value = output[0]
if not value.strip():
return None
return value
def set_prop(self, prop_name, value):
self.shell(['setprop', prop_name, value])

View File

@@ -56,26 +56,32 @@ static int _convert_crlf_to_lf(void) {
// convert CRLF -> LF
ssize_t outlen=0;
ssize_t outmax=0;
for (ssize_t i=0; i<inlen; i++) {
char c = inbuf[i];
if (sawCR && (c != LF)) {
outbuf[outlen++] = CR;
outbuf[outmax++] = CR;
}
sawCR = false;
if (c == CR) {
sawCR = true;
} else {
outbuf[outlen++] = c;
outbuf[outmax++] = c;
}
}
if (TEMP_FAILURE_RETRY(write(STDOUT_FILENO, outbuf, outlen)) == -1) {
errWrt = "error writing to stdout";
break;
}
ssize_t outlen = 0;
char *outb = &outbuf[0];
do {
if (TEMP_FAILURE_RETRY(outlen = write(STDOUT_FILENO, outb, outmax)) == -1) {
errWrt = "error writing to stdout";
break;
}
outb += outlen;
outmax -= outlen;
} while (outmax > 0);
}
if (sawCR) {

View File

@@ -0,0 +1,304 @@
# Default values used by several dev-scripts.
#
# Current list of platform levels we support
#
# Note: levels 6 and 7 are omitted since they have the same native
# APIs as level 5. Same for levels 10, 11 and 12
#
API_LEVELS="3 4 5 8 9 12 13 14 15 16 17 18 19 21"
FIRST_API64_LEVEL=21
# Default ABIs for the target prebuilt binaries.
PREBUILT_ABIS="armeabi armeabi-v7a x86 mips armeabi-v7a-hard arm64-v8a x86_64 mips64"
# Location of the STLport sources, relative to the NDK root directory
STLPORT_SUBDIR=sources/cxx-stl/stlport
# Location of the GAbi++ sources, relative to the NDK root directory
GABIXX_SUBDIR=sources/cxx-stl/gabi++
# Location of the GNU libstdc++ headers and libraries, relative to the NDK
# root directory.
GNUSTL_SUBDIR=sources/cxx-stl/gnu-libstdc++
# Location of the LLVM libc++ headers and libraries, relative to the NDK
# root directory.
LIBCXX_SUBDIR=sources/cxx-stl/llvm-libc++
# Location of the LLVM libc++abi headers, relative to the NDK # root directory.
LIBCXXABI_SUBDIR=sources/cxx-stl/llvm-libc++abi/libcxxabi
# Location of the libportable sources, relative to the NDK root directory
LIBPORTABLE_SUBDIR=sources/android/libportable
# Location of the gccunwind sources, relative to the NDK root directory
GCCUNWIND_SUBDIR=sources/android/gccunwind
# Location of the compiler-rt sources, relative to the NDK root directory
COMPILER_RT_SUBDIR=sources/android/compiler-rt
# Location of the support sources for libc++, relative to the NDK root directory
SUPPORT_SUBDIR=sources/android/support
# The date to use when downloading toolchain sources from AOSP servers
# Leave it empty for tip of tree.
TOOLCHAIN_GIT_DATE=now
# The space-separated list of all GCC versions we support in this NDK
DEFAULT_GCC_VERSION_LIST="4.8 4.9"
DEFAULT_GCC32_VERSION=4.8
DEFAULT_GCC64_VERSION=4.9
FIRST_GCC32_VERSION=4.8
FIRST_GCC64_VERSION=4.9
DEFAULT_LLVM_GCC32_VERSION=4.8
DEFAULT_LLVM_GCC64_VERSION=4.9
DEFAULT_BINUTILS_VERSION=2.25
DEFAULT_GDB_VERSION=7.7
DEFAULT_MPFR_VERSION=3.1.1
DEFAULT_GMP_VERSION=5.0.5
DEFAULT_MPC_VERSION=1.0.1
DEFAULT_CLOOG_VERSION=0.18.0
DEFAULT_ISL_VERSION=0.11.1
DEFAULT_PPL_VERSION=1.0
DEFAULT_PYTHON_VERSION=2.7.5
DEFAULT_PERL_VERSION=5.16.2
RECENT_BINUTILS_VERSION=2.25
# Default platform to build target binaries against.
DEFAULT_PLATFORM=android-9
# The list of default CPU architectures we support
DEFAULT_ARCHS="arm x86 mips arm64 x86_64 mips64"
# Default toolchain names and prefix
#
# This is used by get_default_toolchain_name_for_arch and get_default_toolchain_prefix_for_arch
# defined below
DEFAULT_ARCH_TOOLCHAIN_NAME_arm=arm-linux-androideabi
DEFAULT_ARCH_TOOLCHAIN_PREFIX_arm=arm-linux-androideabi
DEFAULT_ARCH_TOOLCHAIN_NAME_arm64=aarch64-linux-android
DEFAULT_ARCH_TOOLCHAIN_PREFIX_arm64=aarch64-linux-android
DEFAULT_ARCH_TOOLCHAIN_NAME_x86=x86
DEFAULT_ARCH_TOOLCHAIN_PREFIX_x86=i686-linux-android
DEFAULT_ARCH_TOOLCHAIN_NAME_x86_64=x86_64
DEFAULT_ARCH_TOOLCHAIN_PREFIX_x86_64=x86_64-linux-android
DEFAULT_ARCH_TOOLCHAIN_NAME_mips=mipsel-linux-android
DEFAULT_ARCH_TOOLCHAIN_PREFIX_mips=mipsel-linux-android
DEFAULT_ARCH_TOOLCHAIN_NAME_mips64=mips64el-linux-android
DEFAULT_ARCH_TOOLCHAIN_PREFIX_mips64=mips64el-linux-android
# The space-separated list of all LLVM versions we support in NDK
DEFAULT_LLVM_VERSION_LIST="3.6 3.5"
# The default LLVM version (first item in the list)
DEFAULT_LLVM_VERSION=$(echo "$DEFAULT_LLVM_VERSION_LIST" | tr ' ' '\n' | head -n 1)
# The default URL to download the LLVM tar archive
DEFAULT_LLVM_URL="http://llvm.org/releases"
# The list of default host NDK systems we support
DEFAULT_SYSTEMS="linux-x86 windows darwin-x86"
# The default issue tracker URL
DEFAULT_ISSUE_TRACKER_URL="http://source.android.com/source/report-bugs.html"
# Return the default gcc version for a given architecture
# $1: Architecture name (e.g. 'arm')
# Out: default arch-specific gcc version
get_default_gcc_version_for_arch ()
{
case $1 in
*64) echo $DEFAULT_GCC64_VERSION ;;
*) echo $DEFAULT_GCC32_VERSION ;;
esac
}
# Return the first gcc version for a given architecture
# $1: Architecture name (e.g. 'arm')
# Out: default arch-specific gcc version
get_first_gcc_version_for_arch ()
{
case $1 in
*64) echo $FIRST_GCC64_VERSION ;;
*) echo $FIRST_GCC32_VERSION ;;
esac
}
# Return default NDK ABI for a given architecture name
# $1: Architecture name
# Out: ABI name
get_default_abi_for_arch ()
{
local RET
case $1 in
arm)
RET="armeabi"
;;
arm64)
RET="arm64-v8a"
;;
x86|x86_64|mips|mips64)
RET="$1"
;;
mips32r6)
RET="mips"
;;
*)
2> echo "ERROR: Unsupported architecture name: $1, use one of: arm arm64 x86 x86_64 mips mips64"
exit 1
;;
esac
echo "$RET"
}
# Retrieve the list of default ABIs supported by a given architecture
# $1: Architecture name
# Out: space-separated list of ABI names
get_default_abis_for_arch ()
{
local RET
case $1 in
arm)
RET="armeabi armeabi-v7a armeabi-v7a-hard"
;;
arm64)
RET="arm64-v8a"
;;
x86|x86_64|mips|mips32r6|mips64)
RET="$1"
;;
*)
2> echo "ERROR: Unsupported architecture name: $1, use one of: arm arm64 x86 x86_64 mips mips64"
exit 1
;;
esac
echo "$RET"
}
# Return toolchain name for given architecture and GCC version
# $1: Architecture name (e.g. 'arm')
# $2: optional, GCC version (e.g. '4.8')
# Out: default arch-specific toolchain name (e.g. 'arm-linux-androideabi-$GCC_VERSION')
# Return empty for unknown arch
get_toolchain_name_for_arch ()
{
if [ ! -z "$2" ] ; then
eval echo \"\${DEFAULT_ARCH_TOOLCHAIN_NAME_$1}-$2\"
else
eval echo \"\${DEFAULT_ARCH_TOOLCHAIN_NAME_$1}\"
fi
}
# Return the default toolchain name for a given architecture
# $1: Architecture name (e.g. 'arm')
# Out: default arch-specific toolchain name (e.g. 'arm-linux-androideabi-$GCCVER')
# Return empty for unknown arch
get_default_toolchain_name_for_arch ()
{
local GCCVER=$(get_default_gcc_version_for_arch $1)
eval echo \"\${DEFAULT_ARCH_TOOLCHAIN_NAME_$1}-$GCCVER\"
}
# Return the default toolchain program prefix for a given architecture
# $1: Architecture name
# Out: default arch-specific toolchain prefix (e.g. arm-linux-androideabi)
# Return empty for unknown arch
get_default_toolchain_prefix_for_arch ()
{
eval echo "\$DEFAULT_ARCH_TOOLCHAIN_PREFIX_$1"
}
# Get the list of all toolchain names for a given architecture
# $1: architecture (e.g. 'arm')
# $2: comma separated versions (optional)
# Out: list of toolchain names for this arch (e.g. arm-linux-androideabi-4.8 arm-linux-androideabi-4.9)
# Return empty for unknown arch
get_toolchain_name_list_for_arch ()
{
local PREFIX VERSION RET ADD FIRST_GCC_VERSION VERSIONS
PREFIX=$(eval echo \"\$DEFAULT_ARCH_TOOLCHAIN_NAME_$1\")
if [ -z "$PREFIX" ]; then
return 0
fi
RET=""
FIRST_GCC_VERSION=$(get_first_gcc_version_for_arch $1)
ADD=""
VERSIONS=$(commas_to_spaces $2)
if [ -z "$VERSIONS" ]; then
VERSIONS=$DEFAULT_GCC_VERSION_LIST
else
ADD="yes" # include everything we passed explicitly
fi
for VERSION in $VERSIONS; do
if [ -z "$ADD" -a "$VERSION" = "$FIRST_GCC_VERSION" ]; then
ADD="yes"
fi
if [ -z "$ADD" ]; then
continue
fi
RET=$RET" $PREFIX-$VERSION"
done
RET=${RET## }
echo "$RET"
}
# Return the binutils version to be used by default when
# building a given version of GCC. This is needed to ensure
# we use binutils-2.19 when building gcc-4.4.3 for ARM and x86,
# and later binutils in other cases (mips, or gcc-4.6+).
#
# Note that technically, we could use latest binutils for all versions of
# GCC, however, in NDK r7, we did build GCC 4.4.3 with binutils-2.20.1
# and this resulted in weird C++ debugging bugs. For NDK r7b and higher,
# binutils was reverted to 2.19, to ensure at least
# feature/bug compatibility.
#
# $1: toolchain with version numer (e.g. 'arm-linux-androideabi-4.8')
#
get_default_binutils_version_for_gcc ()
{
echo "$DEFAULT_BINUTILS_VERSION"
}
# Return the binutils version to be used by default when
# building a given version of llvm. For llvm-3.4 or later,
# we use binutils-2.23+ to ensure the LLVMgold.so could be
# built properly. For llvm-3.3, we use binutils-2.21 as default.
#
# $1: toolchain with version numer (e.g. 'llvm-3.3')
#
get_default_binutils_version_for_llvm ()
{
echo "$DEFAULT_BINUTILS_VERSION"
}
# Return the gdb version to be used by default when building a given
# version of GCC.
#
# $1: toolchain with version numer (e.g. 'arm-linux-androideabi-4.8')
#
get_default_gdb_version_for_gcc ()
{
echo "$DEFAULT_GDB_VERSION"
}
# Return the gdbserver version to be used by default when building a given
# version of GCC.
#
# $1: toolchain with version numer (e.g. 'arm-linux-androideabi-4.8')
#
get_default_gdbserver_version_for_gcc ()
{
echo "$DEFAULT_GDB_VERSION"
}

View File

@@ -0,0 +1,980 @@
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# A collection of shell function definitions used by various build scripts
# in the Android NDK (Native Development Kit)
#
# Get current script name into PROGNAME
PROGNAME=`basename $0`
# Find the Android NDK root, assuming we are invoked from a script
# within its directory structure.
#
# $1: Variable name that will receive the path
# $2: Path of invoking script
find_ndk_root ()
{
# Try to auto-detect the NDK root by walking up the directory
# path to the current script.
local PROGDIR="`dirname \"$2\"`"
while [ -n "1" ] ; do
if [ -d "$PROGDIR/build/core" ] ; then
break
fi
if [ -z "$PROGDIR" -o "$PROGDIR" = '/' ] ; then
return 1
fi
PROGDIR="`cd \"$PROGDIR/..\" && pwd`"
done
eval $1="$PROGDIR"
}
# Put location of Android NDK into ANDROID_NDK_ROOT and
# perform a tiny amount of sanity check
#
if [ -z "$ANDROID_NDK_ROOT" ] ; then
find_ndk_root ANDROID_NDK_ROOT "$0"
if [ $? != 0 ]; then
echo "Please define ANDROID_NDK_ROOT to point to the root of your"
echo "Android NDK installation."
exit 1
fi
fi
echo "$ANDROID_NDK_ROOT" | grep -q -e " "
if [ $? = 0 ] ; then
echo "ERROR: The Android NDK installation path contains a space !"
echo "Please install to a different location."
exit 1
fi
if [ ! -d $ANDROID_NDK_ROOT ] ; then
echo "ERROR: Your ANDROID_NDK_ROOT variable does not point to a directory."
exit 1
fi
if [ ! -f $ANDROID_NDK_ROOT/build/tools/ndk-common.sh ] ; then
echo "ERROR: Your ANDROID_NDK_ROOT variable does not point to a valid directory."
exit 1
fi
## Use DRYRUN to find out top-level commands.
DRYRUN=${DRYRUN-no}
## Logging support
##
VERBOSE=${VERBOSE-yes}
VERBOSE2=${VERBOSE2-no}
# If NDK_LOGFILE is defined in the environment, use this as the log file
TMPLOG=
if [ -n "$NDK_LOGFILE" ] ; then
mkdir -p `dirname "$NDK_LOGFILE"` && touch "$NDK_LOGFILE"
TMPLOG="$NDK_LOGFILE"
fi
# Setup a log file where all log() and log2() output will be sent
#
# $1: log file path (optional)
#
setup_default_log_file ()
{
if [ -n "$NDK_LOGFILE" ] ; then
return
fi
if [ -n "$1" ] ; then
NDK_LOGFILE="$1"
else
NDK_LOGFILE=/tmp/ndk-log-$$.txt
fi
export NDK_LOGFILE
TMPLOG="$NDK_LOGFILE"
rm -rf "$TMPLOG" && mkdir -p `dirname "$TMPLOG"` && touch "$TMPLOG"
echo "To follow build in another terminal, please use: tail -F $TMPLOG"
}
dump ()
{
if [ -n "$TMPLOG" ] ; then
echo "$@" >> $TMPLOG
fi
echo "$@"
}
dump_n ()
{
if [ -n "$TMPLOG" ] ; then
printf %s "$@" >> $TMPLOG
fi
printf %s "$@"
}
log ()
{
if [ "$VERBOSE" = "yes" ] ; then
echo "$@"
else
if [ -n "$TMPLOG" ] ; then
echo "$@" >> $TMPLOG
fi
fi
}
log_n ()
{
if [ "$VERBOSE" = "yes" ] ; then
printf %s "$@"
else
if [ -n "$TMPLOG" ] ; then
printf %s "$@" >> $TMPLOG
fi
fi
}
log2 ()
{
if [ "$VERBOSE2" = "yes" ] ; then
echo "$@"
else
if [ -n "$TMPLOG" ] ; then
echo "$@" >> $TMPLOG
fi
fi
}
run ()
{
if [ "$DRYRUN" = "yes" ] ; then
echo "## SKIP COMMAND: $@"
elif [ "$VERBOSE" = "yes" ] ; then
echo "## COMMAND: $@"
"$@" 2>&1
else
if [ -n "$TMPLOG" ] ; then
echo "## COMMAND: $@" >> $TMPLOG
"$@" >>$TMPLOG 2>&1
else
"$@" > /dev/null 2>&1
fi
fi
}
run2 ()
{
if [ "$DRYRUN" = "yes" ] ; then
echo "## SKIP COMMAND: $@"
elif [ "$VERBOSE2" = "yes" ] ; then
echo "## COMMAND: $@"
"$@" 2>&1
elif [ "$VERBOSE" = "yes" ]; then
echo "## COMMAND: $@"
if [ -n "$TMPLOG" ]; then
echo "## COMMAND: $@" >> $TMPLOG
"$@" >>$TMPLOG 2>&1
else
"$@" > /dev/null 2>&1
fi
else
if [ -n "$TMPLOG" ]; then
"$@" >>$TMPLOG 2>&1
else
"$@" > /dev/null 2>&1
fi
fi
}
panic ()
{
dump "ERROR: $@"
exit 1
}
fail_panic ()
{
if [ $? != 0 ] ; then
dump "ERROR: $@"
exit 1
fi
}
fail_warning ()
{
if [ $? != 0 ] ; then
dump "WARNING: $@"
fi
}
## Utilities
##
# Return the value of a given named variable
# $1: variable name
#
# example:
# FOO=BAR
# BAR=ZOO
# echo `var_value $FOO`
# will print 'ZOO'
#
var_value ()
{
# find a better way to do that ?
eval echo "$`echo $1`"
}
# convert to uppercase
# assumes tr is installed on the platform ?
#
to_uppercase ()
{
echo $1 | tr "[:lower:]" "[:upper:]"
}
## First, we need to detect the HOST CPU, because proper HOST_ARCH detection
## requires platform-specific tricks.
##
HOST_EXE=""
HOST_OS=`uname -s`
case "$HOST_OS" in
Darwin)
HOST_OS=darwin
;;
Linux)
# note that building 32-bit binaries on x86_64 is handled later
HOST_OS=linux
;;
FreeBsd) # note: this is not tested
HOST_OS=freebsd
;;
CYGWIN*|*_NT-*)
HOST_OS=windows
HOST_EXE=.exe
if [ "x$OSTYPE" = xcygwin ] ; then
HOST_OS=cygwin
fi
;;
esac
log2 "HOST_OS=$HOST_OS"
log2 "HOST_EXE=$HOST_EXE"
## Now find the host architecture. This must correspond to the bitness of
## the binaries we're going to run with this NDK. Certain platforms allow
## you to use a 64-bit kernel with a 32-bit userland, and unfortunately
## commands like 'uname -m' only report the kernel bitness.
##
HOST_ARCH=`uname -m`
case "$HOST_ARCH" in
i?86) HOST_ARCH=x86
# "uname -m" reports i386 on Snow Leopard even though its architecture is
# 64-bit. In order to use it to build 64-bit toolchains we need to fix the
# reporting anomoly here.
if [ "$HOST_OS" = darwin ] ; then
if ! echo __LP64__ | (CCOPTS= gcc -E - 2>/dev/null) | grep -q __LP64__ ; then
# or if gcc -dM -E - < /dev/null | grep -q __LP64__; then
HOST_ARCH=x86_64
fi
fi
;;
amd64) HOST_ARCH=x86_64
;;
powerpc) HOST_ARCH=ppc
;;
esac
HOST_FILE_PROGRAM="file"
case "$HOST_OS-$HOST_ARCH" in
linux-x86_64|darwin-x86_64)
## On Linux or Darwin, a 64-bit kernel doesn't mean that the user-land
## is always 32-bit, so use "file" to determine the bitness of the shell
## that invoked us. The -L option is used to de-reference symlinks.
##
## Note that on Darwin, a single executable can contain both x86 and
## x86_64 machine code, so just look for x86_64 (darwin) or x86-64 (Linux)
## in the output.
##
## Also note that some versions of 'file' in MacPort may report erroneous
## result. See http://b.android.com/53769. Use /usr/bin/file if exists.
if [ "$HOST_OS" = "darwin" ]; then
SYSTEM_FILE_PROGRAM="/usr/bin/file"
test -x "$SYSTEM_FILE_PROGRAM" && HOST_FILE_PROGRAM="$SYSTEM_FILE_PROGRAM"
fi
"$HOST_FILE_PROGRAM" -L "$SHELL" | grep -q "x86[_-]64"
if [ $? != 0 ]; then
# $SHELL is not a 64-bit executable, so assume our userland is too.
log2 "Detected 32-bit userland on 64-bit kernel system!"
HOST_ARCH=x86
fi
;;
esac
log2 "HOST_ARCH=$HOST_ARCH"
# at this point, the supported values for HOST_ARCH are:
# x86
# x86_64
# ppc
#
# other values may be possible but haven't been tested
#
# at this point, the value of HOST_OS should be one of the following:
# linux
# darwin
# windows (MSys)
# cygwin
#
# Note that cygwin is treated as a special case because it behaves very differently
# for a few things. Other values may be possible but have not been tested
#
# define HOST_TAG as a unique tag used to identify both the host OS and CPU
# supported values are:
#
# linux-x86
# linux-x86_64
# darwin-x86
# darwin-x86_64
# darwin-ppc
# windows
# windows-x86_64
#
# other values are possible but were not tested.
#
compute_host_tag ()
{
HOST_TAG=${HOST_OS}-${HOST_ARCH}
# Special case for windows-x86 => windows
case $HOST_TAG in
windows-x86|cygwin-x86)
HOST_TAG="windows"
;;
esac
log2 "HOST_TAG=$HOST_TAG"
}
compute_host_tag
# Compute the number of host CPU cores an HOST_NUM_CPUS
#
case "$HOST_OS" in
linux)
HOST_NUM_CPUS=`cat /proc/cpuinfo | grep processor | wc -l`
;;
darwin|freebsd)
HOST_NUM_CPUS=`sysctl -n hw.ncpu`
;;
windows|cygwin)
HOST_NUM_CPUS=$NUMBER_OF_PROCESSORS
;;
*) # let's play safe here
HOST_NUM_CPUS=1
esac
log2 "HOST_NUM_CPUS=$HOST_NUM_CPUS"
# If BUILD_NUM_CPUS is not already defined in your environment,
# define it as the double of HOST_NUM_CPUS. This is used to
# run Make commands in parralles, as in 'make -j$BUILD_NUM_CPUS'
#
if [ -z "$BUILD_NUM_CPUS" ] ; then
BUILD_NUM_CPUS=`expr $HOST_NUM_CPUS \* 2`
fi
log2 "BUILD_NUM_CPUS=$BUILD_NUM_CPUS"
## HOST TOOLCHAIN SUPPORT
##
# force the generation of 32-bit binaries on 64-bit systems
#
FORCE_32BIT=no
force_32bit_binaries ()
{
if [ "$HOST_ARCH" = x86_64 ] ; then
log2 "Forcing generation of 32-bit host binaries on $HOST_ARCH"
FORCE_32BIT=yes
HOST_ARCH=x86
log2 "HOST_ARCH=$HOST_ARCH"
compute_host_tag
fi
}
# On Windows, cygwin binaries will be generated by default, but
# you can force mingw ones that do not link to cygwin.dll if you
# call this function.
#
disable_cygwin ()
{
if [ $HOST_OS = cygwin ] ; then
log2 "Disabling cygwin binaries generation"
CFLAGS="$CFLAGS -mno-cygwin"
LDFLAGS="$LDFLAGS -mno-cygwin"
HOST_OS=windows
compute_host_tag
fi
}
# Various probes are going to need to run a small C program
mkdir -p /tmp/ndk-$USER/tmp/tests
TMPC=/tmp/ndk-$USER/tmp/tests/test-$$.c
TMPO=/tmp/ndk-$USER/tmp/tests/test-$$.o
TMPE=/tmp/ndk-$USER/tmp/tests/test-$$$EXE
TMPL=/tmp/ndk-$USER/tmp/tests/test-$$.log
# cleanup temporary files
clean_temp ()
{
rm -f $TMPC $TMPO $TMPL $TMPE
}
# cleanup temp files then exit with an error
clean_exit ()
{
clean_temp
exit 1
}
# this function will setup the compiler and linker and check that they work as advertised
# note that you should call 'force_32bit_binaries' before this one if you want it to
# generate 32-bit binaries on 64-bit systems (that support it).
#
setup_toolchain ()
{
if [ -z "$CC" ] ; then
CC=gcc
fi
if [ -z "$CXX" ] ; then
CXX=g++
fi
if [ -z "$CXXFLAGS" ] ; then
CXXFLAGS="$CFLAGS"
fi
if [ -z "$LD" ] ; then
LD="$CC"
fi
log2 "Using '$CC' as the C compiler"
# check that we can compile a trivial C program with this compiler
mkdir -p $(dirname "$TMPC")
cat > $TMPC <<EOF
int main(void) {}
EOF
if [ "$FORCE_32BIT" = yes ] ; then
CC="$CC -m32"
CXX="$CXX -m32"
LD="$LD -m32"
compile
if [ $? != 0 ] ; then
# sometimes, we need to also tell the assembler to generate 32-bit binaries
# this is highly dependent on your GCC installation (and no, we can't set
# this flag all the time)
CFLAGS="$CFLAGS -Wa,--32"
compile
fi
fi
compile
if [ $? != 0 ] ; then
echo "your C compiler doesn't seem to work:"
cat $TMPL
clean_exit
fi
log "CC : compiler check ok ($CC)"
# check that we can link the trivial program into an executable
link
if [ $? != 0 ] ; then
OLD_LD="$LD"
LD="$CC"
compile
link
if [ $? != 0 ] ; then
LD="$OLD_LD"
echo "your linker doesn't seem to work:"
cat $TMPL
clean_exit
fi
fi
log2 "Using '$LD' as the linker"
log "LD : linker check ok ($LD)"
# check the C++ compiler
log2 "Using '$CXX' as the C++ compiler"
cat > $TMPC <<EOF
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!" << endl;
return 0;
}
EOF
compile_cpp
if [ $? != 0 ] ; then
echo "your C++ compiler doesn't seem to work"
cat $TMPL
clean_exit
fi
log "CXX : C++ compiler check ok ($CXX)"
# XXX: TODO perform AR checks
AR=ar
ARFLAGS=
}
# try to compile the current source file in $TMPC into an object
# stores the error log into $TMPL
#
compile ()
{
log2 "Object : $CC -o $TMPO -c $CFLAGS $TMPC"
$CC -o $TMPO -c $CFLAGS $TMPC 2> $TMPL
}
compile_cpp ()
{
log2 "Object : $CXX -o $TMPO -c $CXXFLAGS $TMPC"
$CXX -o $TMPO -c $CXXFLAGS $TMPC 2> $TMPL
}
# try to link the recently built file into an executable. error log in $TMPL
#
link()
{
log2 "Link : $LD -o $TMPE $TMPO $LDFLAGS"
$LD -o $TMPE $TMPO $LDFLAGS 2> $TMPL
}
# run a command
#
execute()
{
log2 "Running: $*"
$*
}
# perform a simple compile / link / run of the source file in $TMPC
compile_exec_run()
{
log2 "RunExec : $CC -o $TMPE $CFLAGS $TMPC"
compile
if [ $? != 0 ] ; then
echo "Failure to compile test program"
cat $TMPC
cat $TMPL
clean_exit
fi
link
if [ $? != 0 ] ; then
echo "Failure to link test program"
cat $TMPC
echo "------"
cat $TMPL
clean_exit
fi
$TMPE
}
pattern_match ()
{
echo "$2" | grep -q -E -e "$1"
}
# Let's check that we have a working md5sum here
check_md5sum ()
{
A_MD5=`echo "A" | md5sum | cut -d' ' -f1`
if [ "$A_MD5" != "bf072e9119077b4e76437a93986787ef" ] ; then
echo "Please install md5sum on this machine"
exit 2
fi
}
# Find if a given shell program is available.
# We need to take care of the fact that the 'which <foo>' command
# may return either an empty string (Linux) or something like
# "no <foo> in ..." (Darwin). Also, we need to redirect stderr
# to /dev/null for Cygwin
#
# $1: variable name
# $2: program name
#
# Result: set $1 to the full path of the corresponding command
# or to the empty/undefined string if not available
#
find_program ()
{
local PROG RET
PROG=`which $2 2>/dev/null`
RET=$?
if [ $RET != 0 ]; then
PROG=
fi
eval $1=\"$PROG\"
return $RET
}
prepare_download ()
{
find_program CMD_WGET wget
find_program CMD_CURL curl
find_program CMD_SCRP scp
}
find_pbzip2 ()
{
if [ -z "$_PBZIP2_initialized" ] ; then
find_program PBZIP2 pbzip2
_PBZIP2_initialized="yes"
fi
}
# Download a file with either 'curl', 'wget' or 'scp'
#
# $1: source URL (e.g. http://foo.com, ssh://blah, /some/path)
# $2: target file
download_file ()
{
# Is this HTTP, HTTPS or FTP ?
if pattern_match "^(http|https|ftp):.*" "$1"; then
if [ -n "$CMD_WGET" ] ; then
run $CMD_WGET -O $2 $1
elif [ -n "$CMD_CURL" ] ; then
run $CMD_CURL -o $2 $1
else
echo "Please install wget or curl on this machine"
exit 1
fi
return
fi
# Is this SSH ?
# Accept both ssh://<path> or <machine>:<path>
#
if pattern_match "^(ssh|[^:]+):.*" "$1"; then
if [ -n "$CMD_SCP" ] ; then
scp_src=`echo $1 | sed -e s%ssh://%%g`
run $CMD_SCP $scp_src $2
else
echo "Please install scp on this machine"
exit 1
fi
return
fi
# Is this a file copy ?
# Accept both file://<path> or /<path>
#
if pattern_match "^(file://|/).*" "$1"; then
cp_src=`echo $1 | sed -e s%^file://%%g`
run cp -f $cp_src $2
return
fi
}
# Form the relative path between from one abs path to another
#
# $1 : start path
# $2 : end path
#
# From:
# http://stackoverflow.com/questions/2564634/bash-convert-absolute-path-into-relative-path-given-a-current-directory
relpath ()
{
[ $# -ge 1 ] && [ $# -le 2 ] || return 1
current="${2:+"$1"}"
target="${2:-"$1"}"
[ "$target" != . ] || target=/
target="/${target##/}"
[ "$current" != . ] || current=/
current="${current:="/"}"
current="/${current##/}"
appendix="${target##/}"
relative=''
while appendix="${target#"$current"/}"
[ "$current" != '/' ] && [ "$appendix" = "$target" ]; do
if [ "$current" = "$appendix" ]; then
relative="${relative:-.}"
echo "${relative#/}"
return 0
fi
current="${current%/*}"
relative="$relative${relative:+/}.."
done
relative="$relative${relative:+${appendix:+/}}${appendix#/}"
echo "$relative"
}
# Unpack a given archive
#
# $1: archive file path
# $2: optional target directory (current one if omitted)
#
unpack_archive ()
{
local ARCHIVE="$1"
local DIR=${2-.}
local RESULT TARFLAGS ZIPFLAGS
mkdir -p "$DIR"
if [ "$VERBOSE2" = "yes" ] ; then
TARFLAGS="vxpf"
ZIPFLAGS=""
else
TARFLAGS="xpf"
ZIPFLAGS="q"
fi
case "$ARCHIVE" in
*.zip)
(cd $DIR && run unzip $ZIPFLAGS "$ARCHIVE")
;;
*.tar)
run tar $TARFLAGS "$ARCHIVE" -C $DIR
;;
*.tar.gz)
run tar z$TARFLAGS "$ARCHIVE" -C $DIR
;;
*.tar.bz2)
find_pbzip2
if [ -n "$PBZIP2" ] ; then
run tar --use-compress-prog=pbzip2 -$TARFLAGS "$ARCHIVE" -C $DIR
else
run tar j$TARFLAGS "$ARCHIVE" -C $DIR
fi
# remove ._* files by MacOSX to preserve resource forks we don't need
find $DIR -name "\._*" -exec rm {} \;
;;
*)
panic "Cannot unpack archive with unknown extension: $ARCHIVE"
;;
esac
}
# Pack a given archive
#
# $1: archive file path (including extension)
# $2: source directory for archive content
# $3+: list of files (including patterns), all if empty
pack_archive ()
{
local ARCHIVE="$1"
local SRCDIR="$2"
local SRCFILES
local TARFLAGS ZIPFLAGS
shift; shift;
if [ -z "$1" ] ; then
SRCFILES="*"
else
SRCFILES="$@"
fi
if [ "`basename $ARCHIVE`" = "$ARCHIVE" ] ; then
ARCHIVE="`pwd`/$ARCHIVE"
fi
mkdir -p `dirname $ARCHIVE`
if [ "$VERBOSE2" = "yes" ] ; then
TARFLAGS="vcf"
ZIPFLAGS="-9r"
else
TARFLAGS="cf"
ZIPFLAGS="-9qr"
fi
# Ensure symlinks are stored as is in zip files. for toolchains
# this can save up to 7 MB in the size of the final archive
#ZIPFLAGS="$ZIPFLAGS --symlinks"
case "$ARCHIVE" in
*.zip)
(cd $SRCDIR && run zip $ZIPFLAGS "$ARCHIVE" $SRCFILES)
;;
*.tar)
(cd $SRCDIR && run tar $TARFLAGS "$ARCHIVE" $SRCFILES)
;;
*.tar.gz)
(cd $SRCDIR && run tar z$TARFLAGS "$ARCHIVE" $SRCFILES)
;;
*.tar.bz2)
find_pbzip2
if [ -n "$PBZIP2" ] ; then
(cd $SRCDIR && run tar --use-compress-prog=pbzip2 -$TARFLAGS "$ARCHIVE" $SRCFILES)
else
(cd $SRCDIR && run tar j$TARFLAGS "$ARCHIVE" $SRCFILES)
fi
;;
*)
panic "Unsupported archive format: $ARCHIVE"
;;
esac
}
# Copy a directory, create target location if needed
#
# $1: source directory
# $2: target directory location
#
copy_directory ()
{
local SRCDIR="$1"
local DSTDIR="$2"
if [ ! -d "$SRCDIR" ] ; then
panic "Can't copy from non-directory: $SRCDIR"
fi
log "Copying directory: "
log " from $SRCDIR"
log " to $DSTDIR"
mkdir -p "$DSTDIR" && (cd "$SRCDIR" && 2>/dev/null tar cf - *) | (tar xf - -C "$DSTDIR")
fail_panic "Cannot copy to directory: $DSTDIR"
}
# Move a directory, create target location if needed
#
# $1: source directory
# $2: target directory location
#
move_directory ()
{
local SRCDIR="$1"
local DSTDIR="$2"
if [ ! -d "$SRCDIR" ] ; then
panic "Can't move from non-directory: $SRCDIR"
fi
log "Move directory: "
log " from $SRCDIR"
log " to $DSTDIR"
mkdir -p "$DSTDIR" && (mv "$SRCDIR"/* "$DSTDIR")
fail_panic "Cannot move to directory: $DSTDIR"
}
# This is the same than copy_directory(), but symlinks will be replaced
# by the file they actually point to instead.
copy_directory_nolinks ()
{
local SRCDIR="$1"
local DSTDIR="$2"
if [ ! -d "$SRCDIR" ] ; then
panic "Can't copy from non-directory: $SRCDIR"
fi
log "Copying directory (without symlinks): "
log " from $SRCDIR"
log " to $DSTDIR"
mkdir -p "$DSTDIR" && (cd "$SRCDIR" && tar chf - *) | (tar xf - -C "$DSTDIR")
fail_panic "Cannot copy to directory: $DSTDIR"
}
# Copy certain files from one directory to another one
# $1: source directory
# $2: target directory
# $3+: file list (including patterns)
copy_file_list ()
{
local SRCDIR="$1"
local DSTDIR="$2"
shift; shift;
if [ ! -d "$SRCDIR" ] ; then
panic "Cant' copy from non-directory: $SRCDIR"
fi
log "Copying file: $@"
log " from $SRCDIR"
log " to $DSTDIR"
mkdir -p "$DSTDIR" && (cd "$SRCDIR" && (echo $@ | tr ' ' '\n' | tar cf - -T -)) | (tar xf - -C "$DSTDIR")
fail_panic "Cannot copy files to directory: $DSTDIR"
}
# Rotate a log file
# If the given log file exist, add a -1 to the end of the file.
# If older log files exist, rename them to -<n+1>
# $1: log file
# $2: maximum version to retain [optional]
rotate_log ()
{
# Default Maximum versions to retain
local MAXVER="5"
local LOGFILE="$1"
shift;
if [ ! -z "$1" ] ; then
local tmpmax="$1"
shift;
tmpmax=`expr $tmpmax + 0`
if [ $tmpmax -lt 1 ] ; then
panic "Invalid maximum log file versions '$tmpmax' invalid; defaulting to $MAXVER"
else
MAXVER=$tmpmax;
fi
fi
# Do Nothing if the log file does not exist
if [ ! -f "${LOGFILE}" ] ; then
return
fi
# Rename existing older versions
ver=$MAXVER
while [ $ver -ge 1 ]
do
local prev=$(( $ver - 1 ))
local old="-$prev"
# Instead of old version 0; use the original filename
if [ $ver -eq 1 ] ; then
old=""
fi
if [ -f "${LOGFILE}${old}" ] ; then
mv -f "${LOGFILE}${old}" "${LOGFILE}-${ver}"
fi
ver=$prev
done
}
# Dereference symlink
# $1+: directories
dereference_symlink ()
{
local DIRECTORY SYMLINKS DIR FILE LINK
for DIRECTORY in "$@"; do
if [ -d "$DIRECTORY" ]; then
while true; do
# Find all symlinks in this directory.
SYMLINKS=`find $DIRECTORY -type l`
if [ -z "$SYMLINKS" ]; then
break;
fi
# Iterate symlinks
for SYMLINK in $SYMLINKS; do
if [ -L "$SYMLINK" ]; then
DIR=`dirname "$SYMLINK"`
FILE=`basename "$SYMLINK"`
# Note that if `readlink $FILE` is also a link, we want to deal
# with it in the next iteration. There is potential infinite-loop
# situation for cicular link doesn't exist in our case, though.
(cd "$DIR" && \
LINK=`readlink "$FILE"` && \
test ! -L "$LINK" && \
rm -f "$FILE" && \
cp -a "$LINK" "$FILE")
fi
done
done
fi
done
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,856 +0,0 @@
#!/bin/sh
#
# Copyright (C) 2010 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This wrapper script is used to launch a native debugging session
# on a given NDK application. The application must be debuggable, i.e.
# its android:debuggable attribute must be set to 'true' in the
# <application> element of its manifest.
#
# See docs/NDK-GDB.TXT for usage description. Essentially, you just
# need to launch ndk-gdb from your application project directory
# after doing ndk-build && ant install && <start-application-on-device>
#
PROGDIR=`dirname $0`
PROGDIR=`cd $PROGDIR && pwd -P`
#set -x
# Check if absolute NDK path contain space
#
case $PROGDIR in
*\ *) echo "ERROR: NDK path cannot contain space"
exit 1
;;
esac
NDK_BUILDTOOLS_PATH=$PROGDIR/build/tools
. $PROGDIR/build/tools/prebuilt-common.sh
. $PROGDIR/build/tools/ndk-common.sh
force_32bit_binaries
# Find if a given shell program is available.
# We need to take care of the fact that the 'which <foo>' command
# may return either an empty string (Linux) or something like
# "no <foo> in ..." (Darwin). Also, we need to redirect stderr
# to /dev/null for Cygwin
#
# $1: program name
# Out: program path, or empty string
# Return: 0 on success, != 0 on error
#
find_program ()
{
local PROG RET
PROG=$(which "$1" 2>/dev/null)
RET=$?
if [ $RET != 0 ]; then
PROG=
fi
echo "$PROG"
return $RET
}
quote_spaces ()
{
echo "$@" | sed -e 's! !\ !g'
}
# If ADB_CMD is not defined, try to find a program named 'adb'
# in our path.
ADB_CMD=${ADB_CMD:-$(find_program adb)}
ADB_FLAGS=${ADB_FLAGS:-}
DEVICE_SERIAL=
JDB_CMD=${JDB_CMD:-$(find_program jdb)}
AWK_CMD=${AWK_CMD:-$(find_program awk)}
DEBUG_PORT=5039
JDB_PORT=65534
UNKNOWN_ABI=$(find_ndk_unknown_archs)
# Delay in seconds between launching the activity and attaching gdbserver on it.
# This is needed because there is no way to know when the activity has really
# started, and sometimes this takes a few seconds.
DELAY=2
PARAMETERS=
OPTION_HELP=no
OPTION_PROJECT=
OPTION_FORCE=no
OPTION_ADB=
OPTION_EXEC=
OPTION_START=no
OPTION_LAUNCH=
OPTION_LAUNCH_LIST=no
OPTION_DELAY=
OPTION_WAIT="-D"
OPTION_PACKAGE_NAME=
check_parameter ()
{
if [ -z "$2" ]; then
echo "ERROR: Missing parameter after option '$1'"
exit 1
fi
}
check_adb_flags ()
{
if [ -n "$ADB_FLAGS" ] ; then
echo "ERROR: Only one of -e, -d or -s <serial> can be used at the same time!"
exit 1
fi
}
get_build_var ()
{
if [ -z "$GNUMAKE" ] ; then
GNUMAKE=make
fi
$GNUMAKE --no-print-dir -f $ANDROID_NDK_ROOT/build/core/build-local.mk -C $PROJECT DUMP_$1 | tail -1
}
get_build_var_for_abi ()
{
if [ -z "$GNUMAKE" ] ; then
GNUMAKE=make
fi
$GNUMAKE --no-print-dir -f $ANDROID_NDK_ROOT/build/core/build-local.mk -C $PROJECT DUMP_$1 APP_ABI=$2 | tail -1
}
# Used to run an awk script on the manifest
run_awk_manifest_script ()
{
$AWK_CMD -f $AWK_SCRIPTS/$1 $PROJECT/$MANIFEST
}
if [ "$HOST_OS" = "cygwin" ] ; then
# Return native path representation from cygwin one
# $1: a cygwin-compatible path (e.g. /cygdrive/c/some/thing)
# Return: path in host windows representation, e.g. C:/some/thing
#
# We use mixed mode (i.e. / as the directory separator) because
# all the tools we use recognize it properly, and it avoids lots
# of escaping nonsense associated with "\"
#
native_path ()
{
cygpath -m $1
}
else # HOST_OS != windows
native_path ()
{
echo "$1"
}
fi # HOST_OS != windows
# We need to ensure the ANDROID_NDK_ROOT is absolute, otherwise calls
# to get_build_var, get_build_var_for_abi and run_awk_manifest_script
# might fail, e.g. when invoked with:
#
# cd $NDKROOT
# ./ndk-gdb --project=/path/to/project
#
path_is_absolute ()
{
local P P2
P=$1 # copy path
P2=${P#/} # remove / prefix, if any
[ "$P" != "$P2" ]
}
if ! path_is_absolute "$ANDROID_NDK_ROOT"; then
ANDROID_NDK_ROOT=$(pwd)/$ANDROID_NDK_ROOT
fi
VERBOSE=no
while [ -n "$1" ]; do
opt="$1"
optarg=`expr "x$opt" : 'x[^=]*=\(.*\)'`
case "$opt" in
--help|-h|-\?)
OPTION_HELP=yes
;;
--verbose)
VERBOSE=yes
;;
-s)
check_parameter $1 $2
check_adb_flags
ADB_FLAGS=" -s"
DEVICE_SERIAL=$2
shift
;;
-s*)
check_adb_flags
optarg=`expr -- "$opt" : '-s\(.*\)'`
ADB_FLAGS=" -s"
DEVICE_SERIAL=$optarg
;;
-p)
check_parameter $1 $2
OPTION_PROJECT="$2"
shift
;;
-p*)
optarg=`expr -- "$opt" : '-p\(.*\)'`
OPTION_PROJECT="$optarg"
;;
--exec=*)
OPTION_EXEC="$optarg"
;;
-x)
check_parameter $1 $2
OPTION_EXEC="$2"
shift
;;
-x*)
optarg=`expr -- "$opt" : '-x\(.*\)'`
OPTION_EXEC="$optarg"
;;
-e)
check_adb_flags
ADB_FLAGS=" -e"
;;
-d)
check_adb_flags
ADB_FLAGS=" -d"
;;
--adb=*) # specify ADB command
OPTION_ADB="$optarg"
;;
--awk=*)
AWK_CMD="$optarg"
;;
--project=*)
OPTION_PROJECT="$optarg"
;;
--port=*)
DEBUG_PORT="$optarg"
;;
--force)
OPTION_FORCE="yes"
;;
--launch-list)
OPTION_LAUNCH_LIST="yes"
;;
--launch=*)
OPTION_LAUNCH="$optarg"
;;
--start)
OPTION_START=yes
;;
--delay=*)
OPTION_DELAY="$optarg"
;;
--nowait)
JDB_PORT=
OPTION_WAIT=
;;
--package=*)
OPTION_PACKAGE_NAME="$optarg"
;;
-*) # unknown options
echo "ERROR: Unknown option '$opt', use --help for list of valid ones."
exit 1
;;
*) # Simply record parameter
if [ -z "$PARAMETERS" ] ; then
PARAMETERS="$opt"
else
PARAMETERS="$PARAMETERS $opt"
fi
;;
esac
shift
done
if [ -z "$JDB_CMD" ] && [ -n "$OPTION_WAIT" ]; then
echo "ERROR: 'jdb' not found; you must either install the JDK, or specify --nowait"
exit 1
fi
if [ -n "$JDB_PORT" ] && [ "$JDB_PORT" = "$DEBUG_PORT" ]; then
echo "ERROR: --port specified cannot be $JDB_PORT without --nowait"
exit 1
fi
if [ "$OPTION_HELP" = "yes" ] ; then
echo "Usage: $PROGNAME [options]"
echo ""
echo "Setup a gdb debugging session for your Android NDK application."
echo "Read $$NDK/docs/NDK-GDB.TXT for complete usage instructions."
echo ""
echo "Valid options:"
echo ""
echo " --help|-h|-? Print this help"
echo " --verbose Enable verbose mode"
echo " --force Kill existing debug session if it exists"
echo " --nowait Don't have application wait for debugger to attach"
echo " (This might cause you to miss some early JNI breakpoints)"
echo " --start Launch application instead of attaching to existing one"
echo " --launch=<name> Same as --start, but specify activity name (see below)"
echo " --launch-list List all launchable activity names from manifest"
echo " --delay=<secs> Delay in seconds between activity start and gdbserver attach."
echo " --project=<path> Specify application project path"
echo " -p <path> Same as --project=<path>"
echo " --package=<name> Specify package name"
echo " --port=<port> Use tcp:localhost:<port> to communicate with gdbserver [$DEBUG_PORT]"
echo " --exec=<file> Execute gdb initialization commands in <file> after connection"
echo " -x <file> Same as --exec=<file>"
echo " --adb=<file> Use specific adb command [$ADB_CMD]"
echo " --awk=<file> Use specific awk command [$AWK_CMD]"
echo " -e Connect to single emulator instance"
echo " -d Connect to single target device"
echo " -s <serial> Connect to specific emulator or device"
echo ""
exit 0
fi
log "Android NDK installation path: $ANDROID_NDK_ROOT"
if [ -n "$OPTION_EXEC" ] ; then
if [ ! -f "$OPTION_EXEC" ]; then
echo "ERROR: Invalid initialization file: $OPTION_EXEC"
exit 1
fi
fi
if [ -n "$OPTION_DELAY" ] ; then
DELAY="$OPTION_DELAY"
fi
# Check ADB tool version
if [ -n "$OPTION_ADB" ] ; then
ADB_CMD=$OPTION_ADB
log "Using specific adb command: $ADB_CMD"
else
if [ -z "$ADB_CMD" ] ; then
echo "ERROR: The 'adb' tool is not in your path."
echo " You can change your PATH variable, or use"
echo " --adb=<executable> to point to a valid one."
exit 1
fi
log "Using default adb command: $ADB_CMD"
fi
ADB_CMD=$(quote_spaces $ADB_CMD)
ADB_VERSION=$("$ADB_CMD" version 2>/dev/null)
if [ $? != 0 ] ; then
echo "ERROR: Could not run ADB with: $ADB_CMD"
exit 1
fi
log "ADB version found: $ADB_VERSION"
if [ "x$DEVICE_SERIAL" = "x" ]; then
log "Using ADB flags: $ADB_FLAGS"
else
log "Using ADB flags: $ADB_FLAGS" \"$DEVICE_SERIAL\"
fi
JDB_CMD=$(quote_spaces $JDB_CMD)
log "Using JDB command: $JDB_CMD"
# Run an ADB command with the right ADB flags
# $1+: adb command parameter
adb_cmd ()
{
if [ "x$DEVICE_SERIAL" = "x" ]; then
"$ADB_CMD" $ADB_FLAGS "$@"
else
# NOTE: We escape $ADB_CMD and $DEVICE_SERIAL in case they contains spaces.
"$ADB_CMD" $ADB_FLAGS "$DEVICE_SERIAL" "$@"
fi
}
# Used internally by adb_var_shell and adb_var_shell2.
# $1: 1 to redirect stderr to $1, 0 otherwise.
# $2: Variable name that will contain the result
# $3+: Command options
_adb_var_shell ()
{
# We need a temporary file to store the output of our command
local CMD_OUT RET OUTPUT VARNAME REDIRECT_STDERR
REDIRECT_STDERR=$1
VARNAME=$2
shift; shift;
CMD_OUT=`mktemp /tmp/ndk-gdb-cmdout-XXXXXX`
# Run the command, while storing the standard output to CMD_OUT
# and appending the exit code as the last line.
if [ "$REDIRECT_STDERR" != 0 ]; then
adb_cmd shell "$@" ";" echo \$? | sed -e 's![[:cntrl:]]!!g' > $CMD_OUT 2>&1
else
adb_cmd shell "$@" ";" echo \$? | sed -e 's![[:cntrl:]]!!g' > $CMD_OUT
fi
# Get last line in log, which contains the exit code from the command
RET=`sed -e '$!d' $CMD_OUT`
# Get output, which corresponds to everything except the last line
OUT=`sed -e '$d' $CMD_OUT`
rm -f $CMD_OUT
eval $VARNAME=\"\$OUT\"
return $RET
}
# Run a command through 'adb shell' and captures its standard output
# into a variable. The function's exit code is the same than the command's.
#
# This is required because there is a bug where "adb shell" always returns
# 0 on the host, even if the command fails on the device.
#
# $1: Variable name (e.g. FOO)
# On exit, $FOO is set to the command's standard output
#
# The return status will be 0 (success) if the command succeeded
# or 1 (failure) otherwise.
adb_var_shell ()
{
_adb_var_shell 0 "$@"
}
# A variant of adb_var_shell that stores both stdout and stderr in the output
# $1: Variable name
adb_var_shell2 ()
{
_adb_var_shell 1 "$@"
}
# Return the PID of a given package or program, or 0 if it doesn't run
# $1: Package name ("com.example.hellojni") or program name ("/lib/gdbserver")
# Out: PID number, or 0 if not running
get_pid_of ()
{
adb_cmd shell ps | $AWK_CMD -f $AWK_SCRIPTS/extract-pid.awk -v PACKAGE="$1"
}
# Check the awk tool
AWK_SCRIPTS=$ANDROID_NDK_ROOT/build/awk
AWK_TEST=`$AWK_CMD -f $AWK_SCRIPTS/check-awk.awk`
if [ $? != 0 ] ; then
echo "ERROR: Could not run '$AWK_CMD' command. Do you have it installed properly?"
exit 1
fi
if [ "$AWK_TEST" != "Pass" ] ; then
echo "ERROR: Your version of 'awk' is obsolete. Please use --awk=<file> to point to Nawk or Gawk!"
exit 1
fi
# Name of the manifest file
MANIFEST=AndroidManifest.xml
# Find the root of the application project.
if [ -n "$OPTION_PROJECT" ] ; then
PROJECT=$OPTION_PROJECT
log "Using specified project path: $PROJECT"
if [ ! -d "$PROJECT" ] ; then
echo "ERROR: Your --project option does not point to a directory!"
exit 1
fi
if [ ! -f "$PROJECT/$MANIFEST" ] ; then
echo "ERROR: Your --project does not point to an Android project path!"
echo " It is missing a $MANIFEST file."
exit 1
fi
else
# Assume we are in the project directory
if [ -f "$MANIFEST" ] ; then
PROJECT=.
else
PROJECT=
CURDIR=`pwd`
while [ "$CURDIR" != "/" ] ; do
if [ -f "$CURDIR/$MANIFEST" ] ; then
PROJECT="$CURDIR"
break
fi
CURDIR=`dirname $CURDIR`
done
if [ -z "$PROJECT" ] ; then
echo "ERROR: Launch this script from an application project directory, or use --project=<path>."
exit 1
fi
fi
log "Using auto-detected project path: $PROJECT"
fi
if [ ! -z "$OPTION_PACKAGE_NAME" ]; then
PACKAGE_NAME="$OPTION_PACKAGE_NAME"
log "Using package name: $PACKAGE_NAME"
else
# Extract the package name from the manifest
PACKAGE_NAME=`run_awk_manifest_script extract-package-name.awk`
if [ $? != 0 -o "$PACKAGE_NAME" = "<none>" ] ; then
echo "ERROR: Could not extract package name from $PROJECT/$MANIFEST."
echo " Please check that the file is well-formed!"
exit 1
fi
log "Found package name: $PACKAGE_NAME"
fi
# If --launch-list is used, list all launchable activities, and be done with it
if [ "$OPTION_LAUNCH_LIST" = "yes" ] ; then
log "Extracting list of launchable activities from manifest:"
run_awk_manifest_script extract-launchable.awk
exit 0
fi
APP_ABIS=`get_build_var APP_ABI`
if [ "$APP_ABIS" != "${APP_ABIS%%all*}" ] ; then
# replace first "all" with all available ABIs
ALL_ABIS=`get_build_var NDK_ALL_ABIS`
APP_ABIS_FRONT="${APP_ABIS%%all*}"
APP_ABIS_BACK="${APP_ABIS#*all}"
APP_ABIS="${APP_ABIS_FRONT}${ALL_ABIS}${APP_ABIS_BACK}"
fi
# replace "armeabi-v7a-hard" with "armeabi-v7a"
APP_ABIS=`echo $APP_ABIS | sed -e 's/armeabi-v7a-hard/armeabi-v7a/g'`
log "ABIs targetted by application: $APP_ABIS"
# Check the ADB command, and that we can connect to the device/emulator
ADB_TEST=`adb_cmd shell ls`
if [ $? != 0 ] ; then
echo "ERROR: Could not connect to device or emulator!"
echo " Please check that an emulator is running or a device is connected"
echo " through USB to this machine. You can use -e, -d and -s <serial>"
echo " in case of multiple ones."
exit 1
fi
# Check that the device is running Froyo (API Level 8) or higher
#
adb_var_shell API_LEVEL getprop ro.build.version.sdk
if [ $? != 0 -o -z "$API_LEVEL" ] ; then
echo "ERROR: Could not find target device's supported API level!"
echo "ndk-gdb will only work if your device is running Android 2.2 or higher."
exit 1
fi
log "Device API Level: $API_LEVEL"
if [ "$API_LEVEL" -lt "8" ] ; then
echo "ERROR: ndk-gdb requires a target device running Android 2.2 (API level 8) or higher."
echo "The target device is running API level $API_LEVEL!"
exit 1
fi
# Get the target device's supported ABI(s)
# And check that they are supported by the application
#
COMPAT_ABI=none
# All modern Android images must support ro.product.cpu.abilist32
# and ro.product.cpu.abilist64. Otherwise fall back to obsolete
# ro.product.cpu.abi and ro.product.cpu.abi2
adb_var_shell CPU_ABILIST64 getprop ro.product.cpu.abilist64
adb_var_shell CPU_ABILIST32 getprop ro.product.cpu.abilist32
CPU_ABIS="$CPU_ABILIST64,$CPU_ABILIST32"
if [ -z "$CPU_ABILIST64" ] && [ -z "$CPU_ABILIST32" ] ; then
adb_var_shell CPU_ABI1 getprop ro.product.cpu.abi
adb_var_shell CPU_ABI2 getprop ro.product.cpu.abi2
CPU_ABIS="$CPU_ABI1,$CPU_ABI2"
fi
# Replace all ',' with space and add trailing space to
# ease whole-word matching of APP_ABI
CPU_ABILIST64=$(echo $CPU_ABILIST64 | tr ',' ' ')
CPU_ABILIST32=$(echo $CPU_ABILIST32 | tr ',' ' ')
CPU_ABIS=$(echo $CPU_ABIS | tr ',' ' ')
log "Device CPU ABIs: $CPU_ABIS"
APP_ABIS=$APP_ABIS" "
adb_var_shell BCFILES run-as $PACKAGE_NAME /system/bin/sh -c "ls lib/*.bc"
####if [ $? = 0 ]; then
#### COMPAT_ABI="$UNKNOWN_ABI"
####else
# Assume that compatible ABI is 32-bit
COMPAT_ABI_BITS=32
# First look compatible ABI in the list of 64-bit ABIs
if [ -n "$CPU_ABILIST64" ] ; then
for CPU_ABI64 in $CPU_ABILIST64; do
if [ "$APP_ABIS" != "${APP_ABIS%$CPU_ABI64 *}" ] ; then
COMPAT_ABI=$CPU_ABI64
COMPAT_ABI_BITS=64
break
fi
done
fi
# If we found nothing - look among 32-bit ABIs
if [ "$COMPAT_ABI" = none ] && [ -n "$CPU_ABILIST32" ] ; then
for CPU_ABI32 in $CPU_ABILIST32; do
if [ "$APP_ABIS" != "${APP_ABIS%$CPU_ABI32 *}" ] ; then
COMPAT_ABI=$CPU_ABI32
break
fi
done
fi
# Lastly, lets check ro.product.cpu.abi and ro.product.cpu.abi2
if [ "$COMPAT_ABI" = none ] && [ -z "$CPU_ABILIST64" ] && [ -z "$CPU_ABILIST32" ]; then
for CPU_ABI in $CPU_ABIS; do
if [ "$APP_ABIS" != "${APP_ABIS%$CPU_ABI *}" ] ; then
COMPAT_ABI=$CPU_ABI
break
fi
done
fi
####fi
if [ "$COMPAT_ABI" = none ] ; then
COMPAT_ABI='armeabi'
fi
log "Compatible device ABI: $COMPAT_ABI"
# Get information from the build system
GDBSETUP_INIT=`get_build_var_for_abi NDK_APP_GDBSETUP $COMPAT_ABI`
log "Using gdb setup init: $GDBSETUP_INIT"
# Find the prefix for gdb-client
if [ "$COMPAT_ABI" != "$UNKNOWN_ABI" ]; then
TOOLCHAIN_PREFIX=`get_build_var_for_abi TOOLCHAIN_PREFIX $COMPAT_ABI`
else
TOOLCHAIN_ABI=$(echo $CPU_ABIS | awk '{print $NF}')
TOOLCHAIN_PREFIX=`get_build_var_for_abi TOOLCHAIN_PREFIX $TOOLCHAIN_ABI`
fi
log "Using toolchain prefix: $TOOLCHAIN_PREFIX"
APP_OUT=`get_build_var_for_abi TARGET_OUT $COMPAT_ABI`
log "Using app out directory: $APP_OUT"
# Check that the application is debuggable, or nothing will work
####DEBUGGABLE=`run_awk_manifest_script extract-debuggable.awk`
####RET=$?
####log "Found debuggable flag: $DEBUGGABLE"
####if [ "$RET" != 0 -o "$DEBUGGABLE" != "true" ] ; then
#### # If gdb.setup exists, then we built with 'ndk-build NDK_DEBUG=1' and it's
#### # ok to not have android:debuggable set to true in the original manifest.
#### # However, if this is not the case, then complain!!
#### if [ -f $PROJECT/libs/$COMPAT_ABI/gdb.setup ] ; then
#### log "Found gdb.setup under libs/$COMPAT_ABI, assuming app was built with NDK_DEBUG=1"
#### else
#### echo "ERROR: Package $PACKAGE_NAME is not debuggable ! You can fix that in two ways:"
#### echo ""
#### echo " - Rebuilt with the NDK_DEBUG=1 option when calling 'ndk-build'."
#### echo ""
#### echo " - Modify your manifest to set android:debuggable attribute to \"true\","
#### echo " then rebuild normally."
#### echo ""
#### echo "After one of these, re-install to the device!"
#### exit 1
#### fi
####else
# DEBUGGABLE is true in the manifest. Let's check that the user didn't change the
# debuggable flag in the manifest without calling ndk-build afterwards.
if [ ! -f $PROJECT/libs/$COMPAT_ABI/gdb.setup ] ; then
echo "ERROR: Could not find gdb.setup under $PROJECT/libs/$COMPAT_ABI"
echo " This usually means you modified your AndroidManifest.xml to set"
echo " the android:debuggable flag to 'true' but did not rebuild the"
echo " native binaries. Please call 'ndk-build' to do so,"
echo " *then* re-install to the device!"
exit 1
fi
####fi
# Find the <dataDir> of the package on the device
adb_var_shell2 DATA_DIR run-as $PACKAGE_NAME /system/bin/sh -c pwd
if [ $? != 0 -o -z "$DATA_DIR" ] ; then
echo "ERROR: Could not extract package's data directory. Are you sure that"
echo " your installed application is debuggable?"
exit 1
fi
log "Found data directory: '$DATA_DIR'"
# Let's check that 'gdbserver' is properly installed on the device too. If 'gdbserver'
# is not there, push 'gdbserver' found in prebuilt.
#
DEVICE_GDBSERVER=$DATA_DIR/lib/gdbserver
adb_var_shell2 GDBSERVER_RESULT ls $DEVICE_GDBSERVER
if [ $? != 0 ]; then
# Figure out what's the target-arch and find gdbserver in prebuilt.
TARGET_ARCH=none
for ANDROID_ARCH in $ANDROID_NDK_ROOT/prebuilt/android-*; do
ANDROID_ARCH=${ANDROID_ARCH#$ANDROID_NDK_ROOT/prebuilt/android-}
if [ "$COMPAT_ABI" = "$ANDROID_ARCH" ]; then
TARGET_ARCH=$ANDROID_ARCH
break;
fi
done
if [ $TARGET_ARCH != "none" ]; then
DEVICE_GDBSERVER=/data/local/tmp/gdbserver
adb shell mkdir -p /data/local/tmp
adb push ${ANDROID_NDK_ROOT}/prebuilt/android-${TARGET_ARCH}/gdbserver/gdbserver \
$DEVICE_GDBSERVER
log "Push gdbserver in device"
else
echo "ERROR: Non-debuggable application installed on the target device."
echo " Please re-install the debuggable version!"
exit 1
fi
fi
log "Found device gdbserver: $DEVICE_GDBSERVER"
# Launch the activity if needed
if [ "$OPTION_START" = "yes" ] ; then
# If --launch is used, ignore --start, otherwise extract the first
# launchable activity name from the manifest and use it as if --launch=<name>
# was used instead.
#
if [ -z "$OPTION_LAUNCH" ] ; then
OPTION_LAUNCH=`run_awk_manifest_script extract-launchable.awk | sed 2q`
if [ $? != 0 ] ; then
echo "ERROR: Could not extract name of launchable activity from manifest!"
echo " Try to use --launch=<name> directly instead as a work-around."
exit 1
fi
log "Found first launchable activity: $OPTION_LAUNCH"
if [ -z "$OPTION_LAUNCH" ] ; then
echo "ERROR: It seems that your Application does not have any launchable activity!"
echo " Please fix your manifest file and rebuild/re-install your application."
exit 1
fi
fi
fi
if [ -n "$OPTION_LAUNCH" ] ; then
log "Launching activity: $PACKAGE_NAME/$OPTION_LAUNCH"
adb_var_shell2 DUMMY am start $OPTION_WAIT -n $PACKAGE_NAME/$OPTION_LAUNCH
if [ $? != 0 ] ; then
echo "ERROR: Could not launch specified activity: $OPTION_LAUNCH"
echo " Use --launch-list to dump a list of valid values."
exit 1
fi
# Sleep a bit, it sometimes take one second to start properly
# Note that we use the 'sleep' command on the device here.
run adb_cmd shell sleep $DELAY
fi
# Find the PID of the application being run
PID=$(get_pid_of "$PACKAGE_NAME")
RET=$?
log "Found running PID: $PID"
if [ "$RET" != 0 -o "$PID" = "0" ] ; then
echo "ERROR: Could not extract PID of application on device/emulator."
if [ -n "$OPTION_LAUNCH" ] ; then
echo " Weird, this probably means one of these:"
echo ""
echo " - The installed package does not match your current manifest."
echo " - The application process was terminated."
echo ""
echo " Try using the --verbose option and look at its output for details."
else
echo " Are you sure the application is already started?"
echo " Consider using --start or --launch=<name> if not."
fi
exit 1
fi
# Check that there is no other instance of gdbserver running
GDBSERVER_PID=$(get_pid_of lib/gdbserver)
if [ "$GDBSERVER_PID" != "0" ]; then
if [ "$OPTION_FORCE" = "no" ] ; then
echo "ERROR: Another debug session running, Use --force to kill it."
exit 1
fi
log "Killing existing debugging session"
run adb_cmd shell kill -9 $GDBSERVER_PID
fi
# Launch gdbserver now
DEBUG_SOCKET=debug-socket
adb_var_shell2 DUMMY run-as $PACKAGE_NAME $DEVICE_GDBSERVER +$DEBUG_SOCKET --attach $PID &
if [ $? != 0 ] ; then
echo "ERROR: Could not launch gdbserver on the device?"
exit 1
fi
log "Launched gdbserver succesfully."
# Setup network redirection
log "Setup network redirection"
run adb_cmd forward tcp:$DEBUG_PORT localfilesystem:$DATA_DIR/$DEBUG_SOCKET
if [ $? != 0 ] ; then
echo "ERROR: Could not setup network redirection to gdbserver?"
echo " Maybe using --port=<port> to use a different TCP port might help?"
exit 1
fi
# If we are debugging 64-bit app, then we need to pull linker64,
# app_process64 and libc.so from lib64 directory
LINKER_NAME=linker
LIBDIR_NAME=lib
APP_PROCESS_NAME=app_process32
if [ "$COMPAT_ABI_BITS" = 64 ] ; then
LINKER_NAME=linker64
LIBDIR_NAME=lib64
APP_PROCESS_NAME=app_process64
else
# Old 32-bit devices do not have app_process32. Pull
# app_process in this case
adb_var_shell2 DUMMY test -e /system/bin/$APP_PROCESS_NAME
if [ $? != 0 ] ; then
APP_PROCESS_NAME=app_process
fi
fi
# Get the app_server binary from the device
APP_PROCESS=$APP_OUT/app_process
run adb_cmd pull /system/bin/$APP_PROCESS_NAME `native_path $APP_PROCESS`
log "Pulled $APP_PROCESS_NAME from device/emulator."
run adb_cmd pull /system/bin/$LINKER_NAME `native_path $APP_OUT/$LINKER_NAME`
log "Pulled $LINKER_NAME from device/emulator."
run adb_cmd pull /system/$LIBDIR_NAME/libc.so `native_path $APP_OUT/libc.so`
log "Pulled /system/$LIBDIR_NAME/libc.so from device/emulator."
# Setup JDB connection, for --start or --launch
if [ "$OPTION_START" = "yes" ] || [ -n "$OPTION_LAUNCH" ] ; then
if [ -n "$JDB_PORT" ]; then
log "Setup JDB connection"
run adb_cmd forward tcp:$JDB_PORT jdwp:$PID
sleep 1
$JDB_CMD -connect com.sun.jdi.SocketAttach:hostname=localhost,port=$JDB_PORT &
sleep 1
fi
fi
# If we are debugging UNKNOWN_ABI, download compiled *.so from device.
#
if [ "$COMPAT_ABI" = "$UNKNOWN_ABI" ]; then
for bc in $BCFILES; do
log "Pulled $(basename $bc .bc).so from device/emulator."
adb pull $DATA_DIR/lib/$(basename $bc .bc).so $PROJECT/obj/local/$UNKNOWN_ABI/
done
fi
# Now launch the appropriate gdb client with the right init commands
#
GDBCLIENT=${TOOLCHAIN_PREFIX}gdb
GDBSETUP=$APP_OUT/gdb.setup
cp -f $GDBSETUP_INIT $GDBSETUP
#uncomment the following to debug the remote connection only
#echo "set debug remote 1" >> $GDBSETUP
echo "file `native_path $APP_PROCESS`" >> $GDBSETUP
echo "target remote :$DEBUG_PORT" >> $GDBSETUP
if [ -n "$OPTION_EXEC" ] ; then
cat $OPTION_EXEC >> $GDBSETUP
fi
$GDBCLIENT -x `native_path $GDBSETUP`
set +x

Some files were not shown because too many files have changed in this diff Show More