aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authoronefang2020-02-27 11:18:47 +1000
committeronefang2020-02-27 11:18:47 +1000
commit34c5ee4c2a489a506e93d5b303fbc80b263747f0 (patch)
tree53e5f29e16929503a1171a0e251249c0396c5f50
parentAdd a default gloebit config file, coz it doesn't like it if it doesn't exist. (diff)
downloadopensim-SC_OLD-34c5ee4c2a489a506e93d5b303fbc80b263747f0.zip
opensim-SC_OLD-34c5ee4c2a489a506e93d5b303fbc80b263747f0.tar.gz
opensim-SC_OLD-34c5ee4c2a489a506e93d5b303fbc80b263747f0.tar.bz2
opensim-SC_OLD-34c5ee4c2a489a506e93d5b303fbc80b263747f0.tar.xz
The new sledjchisl binary, to replace the management scripts and web stuff.
-rw-r--r--etc/apache2/sites-available/000-default.conf41
-rw-r--r--src/.sledjChisl.conf.lua21
-rw-r--r--src/BOXES.txt1130
-rw-r--r--src/BUGS.txt6
-rw-r--r--src/NOTES.txt183
-rw-r--r--src/README27
-rw-r--r--src/boxes.c2607
-rwxr-xr-xsrc/build.sh67
-rw-r--r--src/dumbsh.c283
-rw-r--r--src/fcgi_SC.c13
-rw-r--r--src/fcgi_SC.h136
-rw-r--r--src/git-sub-modules/README2
-rw-r--r--src/handlekeys.c445
-rw-r--r--src/handlekeys.h76
-rw-r--r--src/script.lua18
-rw-r--r--src/showkey.c149
-rw-r--r--src/sledjchisl.c1297
-rw-r--r--src/toybox.c520
-rw-r--r--src/toybox.h278
-rw-r--r--web/SledjHamr.pngbin0 -> 1655233 bytes
-rw-r--r--web/SledjHamrIcon.pngbin0 -> 95362 bytes
-rw-r--r--web/SledjHamrIconSmall.pngbin0 -> 8535 bytes
-rw-r--r--web/about.html11
-rw-r--r--web/debugStyle.css36
-rw-r--r--web/help.html11
-rw-r--r--web/loginpage.html108
-rw-r--r--web/password_help.html11
-rw-r--r--web/register.html15
-rw-r--r--web/stats.html25
29 files changed, 7516 insertions, 0 deletions
diff --git a/etc/apache2/sites-available/000-default.conf b/etc/apache2/sites-available/000-default.conf
new file mode 100644
index 0000000..2baa892
--- /dev/null
+++ b/etc/apache2/sites-available/000-default.conf
@@ -0,0 +1,41 @@
1<VirtualHost *:80>
2 # The ServerName directive sets the request scheme, hostname and port that
3 # the server uses to identify itself. This is used when creating
4 # redirection URLs. In the context of virtual hosts, the ServerName
5 # specifies what hostname must appear in the request's Host: header to
6 # match this virtual host. For the default virtual host (this file) this
7 # value is not decisive as it is used as a last resort host regardless.
8 # However, you must set it for any further virtual host explicitly.
9 ServerName localhost
10
11 ScriptAlias /fcgi-bin/ "/var/www/fcgi-bin/"
12 <Directory "/var/www/html">
13 AllowOverride All
14 </Directory>
15
16 <Directory "/var/www/fcgi-bin">
17 AllowOverride All
18 Options FollowSymLinks
19 AddHandler fcgid-script .fcgi
20 </Directory>
21
22 ServerAdmin webmaster@localhost
23 DocumentRoot /var/www/html
24
25 # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
26 # error, crit, alert, emerg.
27 # It is also possible to configure the loglevel for particular
28 # modules, e.g.
29 #LogLevel info ssl:warn
30
31 ErrorLog ${APACHE_LOG_DIR}/error.log
32 CustomLog ${APACHE_LOG_DIR}/access.log combined
33
34 # For most configuration files from conf-available/, which are
35 # enabled or disabled at a global level, it is possible to
36 # include a line for only one particular virtual host. For example the
37 # following line enables the CGI configuration for this host only
38 # after it has been globally disabled with "a2disconf".
39 #Include conf-available/serve-cgi-bin.conf
40
41</VirtualHost>
diff --git a/src/.sledjChisl.conf.lua b/src/.sledjChisl.conf.lua
new file mode 100644
index 0000000..07c42cb
--- /dev/null
+++ b/src/.sledjChisl.conf.lua
@@ -0,0 +1,21 @@
1-- sledjChislConfig.lua
2
3-- This works coz LuaJIT automatically loads the jit module.
4if type(jit) == 'table' then
5 io.write('.sledjChisl.conf.lua is being run by ' .. jit.version .. ' under ' .. jit.os .. ' on a ' .. jit.arch .. '\n')
6else
7 io.write('.sledjChisl.conf.lua is being run by Lua version ' .. _VERSION .. '\n')
8end
9
10config =
11{
12 ["scRoot"] = "/opt/opensim_SC";
13 ["scUser"] = "opensimsc";
14 ["Tconsole"] = "SledjChisl";
15 ["Tsocket"] = "caches/opensim-tmux.socket";
16 ["Ttab"] = "SC";
17 ["loadAverageInc"] = 0.7;
18 ["simTimeOut"] = 45;
19 ["webRoot"] = "/var/www";
20}
21return config
diff --git a/src/BOXES.txt b/src/BOXES.txt
new file mode 100644
index 0000000..745b7c9
--- /dev/null
+++ b/src/BOXES.txt
@@ -0,0 +1,1130 @@
1What's needed for MC like program. Call the library toyboxes, and the MC clone toysoldier. B-)
2
3Use ANSI for terminal control.
4UTF-8 support is welcome in toybox where it makes sense, here it makes sense.
5
6Command defining - key, name, script, context.
7 Internal commands for the scripts.
8 Which can depend on the context.
9 Context is important. Gotta handle stuff like vi modes, MC browse / view / edit modes, less search popping into readline mode, etc.
10 Learnable keys ala GIMP.
11
12Split the screen up into boxes.
13 Each box is a context.
14 Current box should have it's box graphics drawn in a different colour to highlight it.
15 Tab/shift-Tab cycles through boxes.
16 Though the editor has it's own use for tab. Ctrl-tab as an alias perhaps?
17 Horizontal / vertical splits, with some sort of size control.
18 Each box can be split h or v once,
19 with a control of what proportion the current box has (initially half),
20 a minimum size set by the contents,
21 and initially a copy of the pointer to the function that supplies it's contents / deals with it's input / includes it's context.
22 Later it can have that pointer set to something else.
23 Any given box can be deleted, which deletes it's sub boxes, drops it's pointer, and merges it's area with the one it split from.
24 See if screen and tmux agree on keys to use for box control.
25 Though, as usual, it's definable, but screen/tmux can be the defaults.
26 Choose to make a box full screen. With menu to select other boxes, or swap back to full set of boxes.
27 Can be without borders and such.
28
29 The borders can be the usual box graphics, +-| characters, or inverse spaces, in that order of priority.
30 Show bits of text on top and bottom borders (left and right sides of them).
31 MC includes a couple of tiny mouse controlled widgets.
32 Perhaps a scroll widget in left or right borders. Emacs dired has that.
33
34 Single line 'boxes', across the entire terminal, or across each box.
35 Should be context sensitive.
36 Menu - a list of commands showing their names and keys, with sub menus.
37 Function keys - Any function keys with commands showing the command names and key.
38 Perhaps could just be a specialized menu.
39 Programmable status line.
40
41 Contents scrolling.
42 Virtual memory buffers.
43 If a file viewer, just mmap it.
44 Editor should be able to only load in bits of a large file, perhaps with a UNDO/REDO buffer.
45 If command output, can create a temp file and mmap it.
46
47 file viewer (less, man).
48 editor (vi, mcedit, nano).
49 Like e3, make generic editor, with pluggable command keys, that kick in depending on how it's called.
50 Uses a file list in a box to select files for opening.
51 Poor mans top and similar commands.
52 Top at least could make use of internal boxes, with the command list using a sortable list.
53 Put command output in boxes. Each line can be selected and operated on.
54 Title bar across top, with click to sort style column names.
55 Each line is a single line 'box'.
56 ls.
57 ls with display options.
58 archive listing
59 find command result.
60 All these can show current directory with diving in and out, or tree and current directory in a sub box.
61 Also, allow an edit mode for editing the file attributes that are displayed, inline and full box if possible.
62 Shell console, with various text substitutions on input.
63 Can be a single line 'box' as well as an ordinary box.
64 Though the ordinary ones show their output in their box,
65 but the single line one should swap to a full screen, which can be a full screen box.
66
67A box has content. Each content type is a context. So we can have -
68 Plain scrollable text view.
69 Fancy text view (hex and so on).
70 Text edit.
71 Directory browse.
72 Script controlled.
73
74Popup widgets centered on box/boxes that they affect.
75 Labels, Text line (with optional history), check boxes, radio buttons, OK/Cancel/etc buttons, popup select list (useful for history to, can be just a menu specilization).
76 Widget sets, though try to keep things simple enough to not need them.
77 Notifications, with the addition of an abort button.
78 If a single widget in the popup, prompt in a single line "box" near the bottom (like nano).
79 Options.
80 Keep options down to a bare minimum.
81 Command params.
82 Fetch command params from internal toybox structures, map them to the appropriate widget.
83 F2 menu - list of commands to apply to file/s or current directory.
84 The list is selectable by file type.
85 Should use the same code as the menu box, and allow sub menus.
86 Context sensitive history lists for selecting stuff.
87 Could also be a widget inside the popup when needed.
88 Search / replace.
89 Delete / save confirmation.
90 Command progress meter.
91 Use xargs, and have it output something useful per 'argument' for the progress meter.
92 xargs has an option to print the command to stdout, and to ask for confirmation per command.
93 xargs can run multiple threads.
94
95Scripting, so we can make things fancy and tie it together.
96 Don't forget to make it 'scriptable' via internal C.
97 MC uses that for the F2 menu, user menu, and archive access.
98 We should use scripts to define most of the above.
99 I'd like to use Lua, but shell is likely a better choice.
100 Coz toybox will have a shell.
101 And MC scripts are mostly shell bits.
102 Should have an actual toyboxes command that you can feed toyboxes scripts into.
103 Might actually get away with using that to define most of MC, AND be the editor plugins.
104 See how far I get, but that's what I'll start with for testing.
105 I should reuse the old emu protocol for this.
106 Not gonna actually sort function names or key combos.
107 A linear search is good enough for keys, they only come in at human speeds.
108 People might want the scripts to run faster, but in toybox we strive for simplicity.
109 Well, maybe a binary search within each modules function block that is sorted by hand in the source code.
110 On the other hand, might be able to make use of the toybox command parsing infrastructure for script functions.
111 Leaving that as a problem for toybox itself.
112 But it's not reusable, it uses globals, and not sure if we can screw with those globals.
113
114NOTE - toybox is designed to only deal with one command per process. So we can't call other toybox commands internally.
115 Or can we? Toysh does it.
116
117Events
118 We need a time event. Top has a resolution of hundredths of a second, though says that only tenths is officially supported.
119 Tenths makes sense for human speed UI. Hundredths makes sense if you want video frame rates. lol
120 Termios only allows tenths of seconds for read anyway.
121 Hmm, seems read() wants to wait for at least one byte, it's a between byte counter. Doh!
122 Select() is likely less simple, poll() the same, and epoll() seems to be linux specific. All allow more precise timeouts.
123 On the other hand, Rob is using poll() in netcat.
124
125Common bits / differences.
126 Initial toybox command arguments.
127 Ability to change those within the editor.
128 Files - passed as arguments, or can add / remove them from the running editor.
129 Process the file through some proggy, or just some function of the editor script.
130 Save / save as / backups.
131 Modelines are actualy discouraged as a security issue by the standard, but encouraged by toybox to use vi modelines.
132 Filename completion.
133 Filename prompts could have a couple of features.
134 Can pass things in and out of a shell command, append to the file, edit a specific fixed section of a file or special file (disk block editing!), or use stdin and stdout (joe in a pipe).
135 Directory / file browsing in a window.
136 Windows per current design.
137 Multiple files in the command line each have their own window.
138 Different / same file in each.
139 Each has it's own cursor / marks / block, etc.
140 Delete / scroll some other window.
141 Open a file in some other window, possibly creating one first.
142 Show one window full screen.
143 Method to show some hidden window, can be hidden if there's not enough space for them all.
144 Buffers - holds the contents of the files being edited / viewed.
145 Attached to windows, but can shift them around?
146 Edit / view / read only, named / unnamed buffers.
147 Special purpose buffers.
148 Emacs has - scratch, help, grep, compile, gdb, man, shell, probably others.
149 Though most of those are just running some other command in a window.
150 Kill ring buffer.
151 Kill goes to the buffer, delete just vanishes, on the other hand, this is really the difference between "cut" and "delete".
152 "Yank" just means "paste from kill buffer" then.
153 Emacs can have different working directory for each buffer, OR one global directory.
154 List them, perform some operation on the members of the list. Go through them all, prompting to save modified buffers.
155 Display text - navigate within it, scroll it in various ways.
156 Many ways to display otherwise unprintable text.
157 Inverted video for high bit characters.
158 Just show high bit characters.
159 UTF8.
160 ^X
161 Could be a problem with "where's my cursor" for the code.
162 Hex mode, wrap mode, raw / parsed mode, as well as formatted / unformatted mode.
163 Parsed mode has the text being processed by some command specified in the config file.
164 Formatted mode converts common formatting stuff to bold / underline.
165 Line numbers.
166 Scrolling can be definable amounts.
167 Some editors count buffer lines, some display lines.
168 Move to top, bottom, middle of screen / line.
169 Marks.
170 One mark and cursor.
171 Hmmm, Emacs uses the idea of a "point" which is between characters, with the "cursor" on the right side.
172 Not sure if this will be a problem.
173 Emacs has only one mark, and everything between point and mark is the "region", a block I think.
174 Multiple marks - numbered, named, just arbitrary, automated marks for various reasons. Line / line and character marks.
175 Next / previous / goto / remove one / all marks.
176 Whitespace / word boundaries / line / paragraph / "sections", etc. Should be definable.
177 Vi has multiples types of all of them. Pffft
178 Smooth scrolling (line by line).
179 Slow serial line support - do we need it?
180 Maybe. B-(
181 Status line.
182 Show cursor position, details of file / character under cursor / working directory. Often used for line input of parameters to.
183 Top of screen, bottom, above the key display in nano. Can have left, middle, right widgets.
184 Nano has essentially two status lines.
185 Expert mode to turn it off, disable it for more screen space.
186 Regexs - basic / extra / extended
187 Replacable stuff in search & replace. Ex/vi has this as an option.
188 Commands - invoked immediately with no echo, or typed via readline.
189 Pre command numbers (usually not echoed) / post command arguments. Also pre command regexs and other things to select the lines to work on.
190 Methods of repeating commands. Repeat last / next command, possibly X times.
191 Direction / motion.
192 Execute line / buffer / file.
193 Select lines, apply command to them.
194 Parameter expansion.
195 Key handling.
196 Bind / learn keys.
197 Emacs has "keymaps" for major and minor modes, as well as a global one.
198 Unbind keys. Mask keys from a lower level, but without actually binding them to anything, a NOP.
199 Command keys can be one or more keys.
200 Emacs and wordstar generally use a control key followed by some other key.
201 Show help page in a window with key bindings. Show binding for specific key.
202 Shortcut keys display. Nano has two lines of 6 each, showing only the most common. MC has one line, showing 10 function keys. No one else cares?
203 Esc key same as Alt / Meta key OR used for function keys OR used by itself. sigh
204 Meta key used to insert high bit characters.
205 Abort current command key.
206 Emacs has a keymap per buffer, which is the keybindings.
207 A global keymap.
208 The buffers major mode keymap.
209 Minor modes can have keymaps that override the major mode keymap when the minor mode is turned on.
210 Del <-> BS swapping.
211 Readline type widget. Called "minibuffer" in emacs.
212 For command parameters, also just go to one at the bottom to type commands into.
213 A fullscreen editor could be considered to just be a stack of these.
214 History, completion, editing, escape from. Position and length.
215 Same editing keys, and treat it just like a one line window.
216 Restricted or different editing keys while in some random line editing mode.
217 Moving readline to some other line (the basis of full screen editing perhaps).
218 Perhaps allow it to autoexpand if the input covers multiple lines.
219 Though that might be better to do as just creating more lines, then moving the readline between them.
220 Still would need the prompt on the top one, and to know to send them all at once when done.
221 How exactly does one create multiple lines?
222 Only way that makes sense is if the ENTER key is different from the "now do this" key.
223 Or wrapping long lines instead of scrolling them.
224 The prompt could include the default in ().
225 Mouse support - left click, double click, scroll wheel.
226 Only used to click on a widget, move cursor, or scroll around.
227 Shift click is used for X cut and paste support, think that just works from the terminal.
228 Shell - In a window / full screen.
229 Make editor a background task with bg / fg standard shell stuff.
230 Pass a block or buffer as stdin. Append / insert stdout to the buffer.
231 The shell output can just appended to the window contents as text that can be edited like every other window. When the cursor is at the bottom of the shell windov, stuff typed in is the next shell command.
232 Simple editing.
233 Move cursor, insert, delete, overwrite.
234 Basic editing.
235 Modes.
236 Vi has a lot of them - command, insert, ex, and moooore.
237 Emacs has definable modes that are a different concept to vi.
238 There are "major" and "minor" modes per buffer.
239 Major modes is for file type specific editing like "editing C, latex, etc" or "Dired".
240 Includes things like indenting, syntax highlighting, function boundaries (for ctags I guess), invoking the right compiler, keymaps, etc.
241 Can include other specialisations, like a python shell, and dired.
242 Minor modes are extras you can add, they seem to be things like "autofill", "wrap", "overwrite", "line numbers", etc.
243 Insert / overwrite mode.
244 Automatic detection of file type (typically source files) to put editor into different modes.
245 Cut, copy, paste, delete - blocks, word / line /etc, to end / beginning line, others.
246 To / from other buffers, files, "clipfile", maybe even the clipboard/s (thought that's an X thing I think).
247 Blocks - cut, copy, move, write to file.
248 Unhighlight block.
249 Search / replace - regex / shell glob / straight text / whole words. Case / charset sensitive. Forward / backward.
250 Regexs - basic / extra / extended
251 File / all files / within selection. Wrap around searching.
252 Incremental. Interactive / all replace. Inverted (find non matches).
253 Highlight / filter found.
254 Regex for the replace bit. Replacable stuff in search & replace. Ex/vi has this as an option.
255 Can search mixed hex and strings.
256 History, repeat, repeat in other direction.
257 Modified state.
258 Can be used by some commands to pester the user.
259 Ability to turn that state off.
260 Tabs / spaces / half tabs. Setting tab size.
261 Advanced editing.
262 Insert a character that is otherwise hard to insert, like command keys.
263 Insert date/time.
264 Quick search for a character in current line / forward / back.
265 "Smart" home, home goes to actual beginning, or first non blank.
266 Replace lots of space horizontally / vertically with just one / zero spaces.
267 Insert empty line/s above / below.
268 End of line space removal.
269 Allow cursor beyond end of line.
270 Visible white space.
271 DOS / Mac line ending convert. On the other hand, might just be good to do that transparently, remember on read, convert on save.
272 Change text encoding.
273 Add a newline at end of file if it's missing one.
274 Think we are doing that anyway, but an option to turn that off might be good.
275 Transpose, upper, lower, reverse case, capitalize words.
276 Adding a prefix / suffix string to selected lines.
277 Indent / outdent / line wrapping / centreing which can be auto / manual. Left and right margins.
278 Column blocks.
279 Deleting might just replace the column block with spaces / tabs.
280 Macros. Various ways of defining / invoking a macro.
281 Nested macros.
282 Auto expanding abbreviations.
283 Auto save after certain commands, or certain time.
284 Multi level undo / redo. Also undo current line (revert it?).
285 Disable undo.
286 Ability to list the undo "records"?
287 Emacs undo boundaries?
288 Spell checker.
289 Pretty printer (likely via shell command like indent).
290 Email quote handling.
291 Count / highlight lines matching or not matching regexes.
292 Sort block.
293 Persistant cursor position and selection.
294 Complete the word being typed based on other words in the file.
295 Code editing.
296 Ctags - basically lookup a symbol (word cursor is on) in the ctags files, which gives you a position in some other file that defines this symbol. Then display this other file somehow, probably allowing editing.
297 Ctags is in the standards, but do we want to write a toybox version?
298 Bracket / character matching. Goto / highlight matching bracket. Auto insert the closing one.
299 Include "insert one, blink the other".
300 Auto detect code block for indenting?
301 Syntax highlighting.
302 Next / previous error (compile errors usually).
303
304Readline.
305 toybox has get_line() and get_rawline(), but they are not interactive readlines.
306 In order to have our readline() be useful for generic use, the keystrokes that do stuff must be definable.
307 So, what do we need?
308 GNU readline has (leaving out a few things) -
309 Editing
310 How is a "word" defined?.
311 Hitting ENTER anywhere in the line submits it. An editor would want to actually insert the ENTER into the text.
312 Move cursor / insert / delete / backspace / undo (all the way back) / revert.
313 Perhaps redo might be nice.
314 Move cursor to start / end of line, forward / back one word, redraw.
315 Cut / copy / paste. Kill ring with ring rotation?
316 Entire line.
317 All spaces around cursor.
318 From cursor to mark.
319 Highlighted block.
320 From cursor to start / end of line.
321 to end of word, or end of next word if between words.
322 to beginning of word, or of the previous word if between words.
323 to previous white space. Different from above coz apparently white space is different from word boundaries. shrugs
324 Numbers in front of commands. Vi and emacs probably need this? More and less "needs" this. Otherwise... eww.
325 For repeats, or sometimes to reverse the direction if it's negative.
326 Insert / overwrite mode.
327 Upper and lower casing characters or words.
328 Transpose characters / words.
329 Marks - setting a mark and moving the cursor to it / swapping with it.
330 Character search within the string - forward / back.
331 Insert comment - bloat we don't need.
332 History.
333 Back / forward one line. Goto start / end of history.
334 Incremental and non incremental searching, forwards or back.
335 During incremental search - ability to abort and restore original line.
336 Remember the last (incremental?) search.
337 Either return the found history (ENTER), or allow editing (editing keys).
338 Option to save edited history lines, and mark them as edited.
339 Show key bindings / macros / etc.
340 Tab completion.
341 List / select completions.
342 Cycle through the matches.
343 Macros!
344 Config files.
345 Not likely to emulate these, we have our own needs, and it's not a standard.
346 Expansions.
347 MC will want argument expansions at least.
348 Though perhaps this is best left to the code that calls this to expand the result.
349
350more
351 Can handle switch between multiple files.
352 Searches are regexs. Can also search for non matches.
353 Multiple marks.
354 Forward and back half screenful, with "half" being the specified number, but defaulting to actual half.
355 Shell commands (not in the standard).
356 Invoke the editor mentioned in EDITOR, or default to vi. Pass line number if it's vi.
357 Print info about file.
358 Ctags.
359 Some commands are single letters, some are ":" followed by a single letter, possible with more text, then ENTER.
360 Or other variations.
361
362less
363 Has bracket matching, but only from top-open / bottom-close.
364 Can search between multiple files, or add more.
365 Files on the command line, add / remove files, just open a new file now.
366 Highlight found text.
367 Filter found lines.
368 Option to NOT do regex search.
369 Change the command line arguments while running.
370 Shell commands with replacable params.
371 Input processor - some proggy that the input file is processed through, the output of that is shown.
372 Um, why not use pipes and input redirection?
373
374ed - obsolete for toybox, ancestor of ex.
375 A line editor.
376 In command mode, type the command, then ENTER.
377 In input mode, type text, with "." on a line by itself to go back to command mode.
378 Uses one or two optional line addresses (can be regex) followed by a single character command, then arguments.
379 The "address" can be various symbols with various meanings, see the manual.
380 Usual basic editing and navigation commands.
381 Join lines.
382 Mark lines.
383 Display lines (with line numbers).
384 Copy / move lines.
385 Apply a command to a bunch of lines matching / not matching the address.
386 Insert a file / write lines to a file.
387 Search and replace.
388 Undo.
389 Shell command, with filename replacement character.
390
391sed
392 Stream editor.
393 Fairly similar to ed, except it applies a script of commands to each line in turn.
394 Branch to a label within the script. Can test if a substitution happened before deciding to branch.
395 Includes a "hold space" for cut and paste type operations, as well as swapping.
396 Can output line numbers.
397 Can read in a file, or write to one.
398 Can be commented.
399
400ex - obsolete for toybox, but part of vi.
401 A line editor, apparently the line oriented editing mode for vi.
402 So while the command itself is obsolete, it's internal stuff might be needed for vi.
403 In fact a lot of the standard for vi just refers to ex.
404 ":" means to perform an ex command from vi. lol
405 Starts in command mode (":" prompt).
406 Basically a "type command then ENTER" command mode.
407 Text input mode (append, insert, change) ended by "." on it's own line.
408 Has line addresses before the commands, similar to ed.
409 Commands have complex parsing requirements. Ewww.
410 Has some really basic command line editing.
411 Has a bunch of buffers, with modes. Commands apply to a named buffer, or the unnamed one if no name is given.
412 Abbreviations and maps. Same thing? They interact. lol
413 Think the difference is that maps don't have to have blank space after them.
414 They expand during typing.
415 Seems that maps are for binding to keys?
416 Set various options.
417 Shell command, with optional interaction.
418 Read commands from a file.
419 Can scan ctags files looking for regexs.
420 Can switch to "open" and "visual" modes, whatever they are.
421 "Visual" mode is just vi.
422 "Open" mode I think is a vi for dumb terminals?
423 Has a concept of "window", also not sure what that is. Might just be the number of terminal lines.
424 Shell escape - pass selected lines to a shell command, replace them with whatever it returns.
425 Shift lines back and forth. Indent and outdent in other words.
426 Execute a buffer as ex commands.
427 Regexs have extra thingies.
428 Replace commands can refer to other bits of text using parameters. See the manual.
429 Autowrite - basicaly save the file after certain commands.
430 Mode that strips out non printables on file read.
431 Can be made ed compatible. lol
432 Can display line numbers.
433 Paragraph boundary pairs can be set.
434 Definable scroll distance.
435 Visual and open modes have "sections", with definable pairs of boundary characters.
436 Can show matching braces.
437 Can optionally warn on some commands if the buffers are modified.
438 Margin auto wrap and end of line blanks removal.
439 Can wrap searches.
440 "modelines" (as used at the top of toybox source files) are apparently is strongly discouraged by the standard. shrugs
441 Overlaping copies are allowed.
442 Automatic marks created sometimes.
443
444vi
445 Notable for it's variety of modes, and it's standard command mode that uses non echoed ordinary keys for common commands.
446 "Visual" editor, a screen oriented superset of ex, that lets you use ex commands.
447 A lot of the standard simply refers to ex.
448 Seems to mostly be similar to ex commands, only done full screen, and with full screen navigation.
449 Has "open" and "visual" modes, but not sure what they are. Also "ex" mode, with means using ex commands.
450 I think "open" mode is for dumb terminals, it's all done on the bottom line, perhaps with full screen redraws.
451 "Visual" mode then must be full screen editing.
452 Text input mode can return to command mode with Esc.
453 Has five kinds of "words" and four kinds of "bigwords", six types of "sections", four types of "paragraphs", and three types of "sentences".
454 There can be a multi digit count before commands, it's not echoed anywhere when not in a command line mode, the command key then does its command count times.
455 For instance typing "12h" does not echo anything, but just moves the cursor back 12 places when the "h" is hit.
456 Lots of those seem to be letters, or control keys, though there are others.
457 [count] ! motion shell-commands <newline> run a shell command, replacing count lines with the output. Looks like you get to type and edit the command while you see it.
458 Move to matching brace.
459 Repeat last command, for specific commands.
460 Move to last context, where I think "context" means where we was before the last command, it gets a special mark.
461 Move to words, bigwords, sections, paragraphs, etc.
462 Reverse case.
463 Find character in current line. Move cursor to before / after specific character.
464 Move to top / middle / bottom of screen.
465 Insert empty line above / below.
466 Paste above / below, depending on if the buffer is line or character mode as well.
467 Replace count characters with the entered character.
468 Undo current line.
469 Some editing command keys while in text input mode.
470
471nano
472 Has a file browser mode.
473 Has a title bar, with three areas for showing status type info.
474 And a status line, which shows messages and lets users type stuff like file names.
475 Optional shortcut lists show 12 of the most common keystrokes in the current mode, and can be mouse clickable.
476 Mouse moves the cursor, double click sets marks.
477 "Smart" home - the usual go to first non blank, or go to actual start of line.
478 Tabs to spaces.
479 Multiple file buffers.
480 Search and replace history.
481 Deal with DOS / Mac line endings sanely.
482 Restricted mode.
483 Smooth scrolling.
484 Setting tab size.
485 Syntax highlighting.
486 Display cursor position.
487 Backspace and delete fixes, coz that's always confusing.
488 Autoindent.
489 Optional line wrap.
490 "Soft wrapping", no idea what that is.
491 External spell checker.
492 Undo and redo.
493 Hard to use method to insert otherwise unusable characters.
494 Optional search case sensitivity.
495 Optional regex searches.
496 Email quote handling.
497 Source code bracket handling of some sort.
498 Turning command line options on and off within the editor.
499 Write selected text to a file.
500
501microemacs (microGNUemacs lol)
502 Go to line number.
503 Scratch buffer, help buffer, grep buffer, compile buffer.
504 Named buffers - per file. Can have their own working directory, or a global one for all.
505 Kill a named buffer, prompting if it's changed.
506 List buffers.
507 Has the concept of "modes", default ones are - fill (wrap), indent, overwrite, and notab.
508 Can set a list of (minor?) modes as default for buffer creation.
509 Run a script (commands from a file).
510 Split windows. B-)
511 Window / buffer specific cursor and mark (depending on which emacs you have).
512 Can swap them.
513 Esc key same as Alt-key.
514 Get help, describe binding, describe key briefly (hit a key, it's binding is displayed).
515 Apropros - prompt the user for a string, open help buffer, list all commands with that string.
516 Auto execute - a shell glob pattern that is matched on files read into buffers, then executes a command.
517 Recenter - position cursor in center of screen / center of line / center of line counted from the bottom.
518 Open line - open up some space by inserting empty lines below cursor, leaving cursor at the top of them.
519 Quoted insert - insert the next key verbatim, ignoring what it's bound to.
520 Universal argument - repeat the next command 4 times, can apply to itself.
521 Toggle read only.
522 Find file read only.
523 Find alternate file - loading a different file into the current buffer, erasing the original buffer contents.
524 Find file in buffer, or load it into new buffer, then switch to it.
525 Find file other window - opens file in new buffer, splitting the window if needed.
526 Delete window.
527 Delete other windows.
528 Split window.
529 Enlarge / shrink window.
530 Next / previous window.
531 Scroll other window - scrolls the next window X pages.
532 Switch to buffer other window - "switch to buffer in another window"?
533 Switch to buffer - ask which buffer should be in this window.
534 Switch to a shell screen, and back again.
535 What cursor position - show a bunch of info about cursor position and what's there.
536 Next / previous error.
537 Dired - basically a multi window MC. lol
538 Save some buffers - looks for buffers that need saving and prompts user.
539 Just one space - delete all white space around cursor, then insert one single space.
540 Query replace - an interactive search and replace. Also a replace all with no interaction.
541 Oh nice - can do a search and replace with a regex for both the search string, AND for the replacable strings (selecting which ones get replaced with that regex).
542 Beginning / end of buffer.
543 Capitalize / upper / lower word.
544 Delete word.
545 Delete blank lines.
546 Delete horizontal / vertical space.
547 Delete leading / trailing space.
548 Delete lines non / matching regex after cursor.
549 Hmm, difference between "kill" and "delete"?
550 "Kill" goes into the kill buffer, "delete" just vanishes.
551 "Yank" inserts things from the kill buffer.
552 Kill buffer is a ring buffer in full emacs, but might not be a ring in microemacs.
553 So it's just an old fashioned concept for "cut and paste".
554 Fill paragraph - just means to wrap and justify it. Command to ask the user what the wrap column is.
555 Copy region as kill.
556 Execute extended command - does the readline thing to ask user for a command and arguments to run.
557 Execute one buffer / line.
558 Not modified - turn off the modified flag in current buffer.
559 Blink and insert - insert a character, then search backwards for it's match (bracket, or the character itself) and blink that.
560 Mini buffer - it's just a readline for command arguments and such, but treated as a one line window, with an uneditable prompt.
561 It does have the trick of expanding to more lines if it's content goes over multiple lines.
562 The prompt can include a default argument in ().
563 Numbers can proceed commands, seems to be what the universal argument is for.
564 Looks like they have a key to introduce them, then get put into the mini buffer.
565 "Digit-argument" and "negative-argument" might be the functions for that.
566 Swap Del and BS.
567 C program "mode" - no idea what that involves.
568 Count regex non / matches.
569 Define key (includes a keymap, what ever they are). Can also undefine a key, and there's a "global" keymap. Can also bind keys in specific modes.
570 Meta key can insert 8 bit characters, or not.
571 Keyboard quit - abort current action.
572 Insert spaces instead of tabs.
573 Toggle overwrite / insert mode.
574 Add a prefix string to lines in a selected region. Or set that string.
575 Show working directory in the status line.
576 Refresh screen, including recomputing window sizes if needed.
577 Scroll window without changing cursor position within window.
578 Toggle read only.
579 Undo boundaries?
580 Undo on/off.
581 List undo records for current buffer, in a new buffer.
582
583wordstar (joe, turbo C) - Using joe, which also comes in emacs and pico flavours.
584 Help window, with commands to page through that help text.
585 Something about "inverting" 8 bit characters, but we want to be 8 bit clean. Not a WordStar thing I think. shrugs
586 Ah, for displaying high bit characters in other languages, normally inverts the display, but "as is" mode just prints them normally.
587 Option to auto append a new line at end of files on save.
588 Option to disable the status line.
589 Windowed & buffered like emacs, but for when it's pretending to be emacs.
590 Can be windowed anyway.
591 Split window on same file.
592 Make one window full screen.
593 Can move to hidden windows (hidden when there's not enough space for them all).
594 Horizontal split only.
595 Height change.
596 Multiple files on the command line go to multiple windows.
597 Option to define how many lines are kept on screen between page up / down commands.
598 Option to not use X lines at the top, for embedding in a BBS.
599 Multi level undo and redo.
600 Scrolling.
601 Automatic detection of source code files, other types get word wraping and autoindent by default.
602 Can set left and right margins for word wrapping.
603 Can center between them to.
604 Can manually indent lines, highlighted blocks, or autodetected code blocks.
605 Command to insert a single space while in overwrite mode.
606 Overwrite mode makes backspace just move left, no deleting.
607 File name completion.
608 History. History is described as a single line read only edit window, just like other windows, so the usual commands will work in them.
609 Status line can be edited in the setup string, to add or remove escape sequences that include various things.
610 Also a command to show the cursor position and character code in the status line.
611 Suspend the proggy and go to shell. Actually, I think most editors have this in some form or another.
612 Most seem to use the shell foreground/background thingy for this.
613 Search prompts for the thing to search, then prompts for a bunch of character flags that are search options.
614 One of the options is if this is a replace command, then it prompts for the replacement text.
615 It's regexs are slash escaped.
616 Highlighted block can be sent through a shell filter command.
617 Highlighted block can be un highlighted.
618 Macros keyed by digit, and can be nested.
619 Repeat command, hit the repeat, type the numbers, hit the command you want repeated.
620 Also works with characters.
621 Column select mode.
622 Delete block command replaces the entire column with spaces and tabs instead of deleting.
623 Ctags support, prompt for symbol (default is word the cursor is on), search ctags files, replace the file in the current window with the file ctags points to, and the cursor position of the symbol definition.
624 Shell in a window.
625 Very intersening, the shell output is just appended to the window contents as text that can be edited like every other window. When the cursor is at the bottom of the shell windov, stuff typed in is the next shell command.
626 Filename prompts have a couple of features. Can pass things in and out of a shell command, append to the file, edit a specific fixed section of a file or special file (disk block editing!), or use stdin and stdout (joe in a pipe).
627 Keys can be bound in the settings file.
628
629mc viewer
630 Hex mode.
631 Can search mixed hex and strings.
632 Wrap mode.
633 Raw / parsed mode toggle, as well as formatted / unformatted mode toggle.
634 Parsed mode has the text being processed by some command specified in the config file.
635 Formatted mode converts common formatting stuff to bold / underline.
636
637mcedit / cool edit
638 User menu - run a script, insert result.
639 Mark columns.
640 Bookmarks, toggle, next, previous, "flush" (meaning to remove all bookmarks.)
641 Copy / cut insert clipfile as well as prompting for a file.
642 Highlight all lines with found text.
643 Search for hexidecimal, within selection, for whole words, and in "all charsets".
644 Goto matching bracket.
645 Show line numbers.
646 Find "declaration", back from and forward to declaration.
647 It's ctags support.
648 Change text encoding.
649 Create, invoke, and delete macros (hotkeys).
650 Spell check.
651 Email current file.
652 Sort selected lines.
653 Insert output of shell command.
654 Format (wrap) paragraph.
655 Run external format script on selection.
656 Insert date/time.
657 Half tabs.
658 Persistant cursor position and selection.
659 Visible white space.
660 Optional go beyond end of line.
661 Learn keys.
662 Syntax highlighting.
663 Edit syntax and menu files.
664
665Make the fancy features optional in the "wrap if(constant) around things so the compiler can optimise it out" way that Rob likes.
666 So the basic compile choices are -
667 File / stdout viewer.
668 Editor (the following options might depend on how the "pluggable" part is done).
669 Minimal vi?
670 Nice to have vi?
671 Minimal emacs?
672 Nice to have emacs?
673 Nano?
674 Minimal mcedit/cooledit?
675 Nice to have mcedit/cooledit?
676 Lists.
677 Simple top like.
678 Fancy top like.
679 Simple shell.
680 Fancy shell.
681 Basic MC.
682 Full on MC with all bells and whistles.
683 With the fancy compile options being (only if they are substantial amounts of code to support) -
684 Full popups or mere single line prompts?
685 One pane, or multiple panes?
686 Function key single line 'box'?
687 Menu?
688 Status line?
689 Border texts?
690 Border widgets?
691 Sortable lists?
692 History lists?
693 Progress bar?
694 Shell text substitutions?
695 Scriptable?
696 Learnable keys?
697
698Probably got most of what we need to do screen / tmux / multitail, and other full screen terminal stuffs.
699
700Are we ncurses yet? Or twin? lol
701
702------------------------------------------------------------------------------------------------------
703
704NOTES
705
706From Rob -
707----------
708
709On 06/21/2012 09:28 AM, David Seikel wrote:
710> What are the chances that get_optflags() could be made re usable by
711> toys?
712
713Well, right now you can put a NULL as your optstring and then set
714which->options yourself and call get_optflags().
715
716> For toys that need to make their own little scripting system for
717> example. Currently it seems to want to use the toys global, and I'm
718> not sure if it's safe to screw with that after pulling all of my toys
719> options out of it.
720>
721> Would it get reused like that for shell internal commands?
722
723Already does. In toys/toysh.c function run_pipeline() look for the
724TOYFLAG_NOFORK bit.
725
726This probably needs to be genericized somewhere in lib, but I never got
727around to it.
728
729From Rob -
730----------
731
732I need to write a getline() with cursor control, which means I need to
733query the tty size. The magic for that is:
734
735struct winsize tty = { 0, 0, 0, 0 };
736int ret = ioctl(1, TIOCGWINSZ, &tty);
737
738plus $COLUMNS and $LINES, plus echo -e "\e[s\e[999C\e[999B\e[6n\e[u"
739
740Add command history parsing to getline().
741
742From Rob -
743----------
744
745Digging up ancient issues from toybox development, one of which is an
746interesting design issue with querying the terminal size.
747
748When you're on a serial console (happens a lot in the embedded world),
749the local tty device doesn't know the width and height of the window at
750the far end, so ioctl(TIOCGWINSZ) can't report it to you.
751
752If the term program at the other end supports ansi escape sequences
753(everything does, including xterms), there's an escape sequence you can
754use to ask it, but there's multiple levels of non-obvious to the
755implementation.
756
757The escape sequence itself is "\e[6n", to which the terminal program
758responds with another escape sequence, "\e[YY;XXR" where the "XX" part
759is a decimal number with the current cursor's Y location, and XX is the
760cursor X location. (Both decimal, top left is 1;1.)
761
762Since what we want is the size of the screen, we wrap that in some more
763commands, saving the current position, moving to the cursor 999 down and
764999 to the right (which should stick it at the lower right corner),
765query that position, and the jump back to the saved location. The full
766escape sequence is therefore "\e[s\e[999C\e[999B\e[6n\e[u".
767
768The problem is, the response comes back from the terminal program on
769stdin: along with whatever else is coming in on stdin. There could be a
770delay of a significant fraction of a second (especially through a rial
771port, even when you aren't overcommitted and swapping), and there's no
772guarantee the terminal will actually respond. So blocking and waiting
773for a response isn't the greatest idea, and can eat other input the user
774has queued up.
775
776This means you need to do a nonblocking read, assemble an ansi sequence
777a piece at a time (luckily the term programs generate these atomically
778so you don't get part of an ansi sequence with user-typed keys in the
779middle of it), and keep any other data you get for use later. The
780logical place to do this is in the line editing code the shell has to
781have anyway, which responds to cursor keys, page up and down, and so on.
782
783From Rob -
784----------
785
786Less has to get it _right_, as does line editing for a command shell.
787Even before you get to cursor up: unix tty handling is epically crappy.
788My commodore 64 could backspace past the left edge of the screen and
789continue from the right edge one line up. A TRS-80 could do this. Unix
790derivatives _can't_, you have to know when you're at the left edge of
791the screen and do the ansi sequences for "cursor up one, jump right
792999". Which means you have to know when backspace puts you at the left
793edge of the screen, which means you need to know when outputting normal
794characters put you off the _right_ edge of the screen...
795
796That's right: if you don't know what your current screen size is, your
797command shell can't backspace past a line wrap. Welcome to unix
798terminal handling.
799
800From Rob -
801----------
802
803Here's a fun one:
804
805cat /proc/mounts | less
806
807When less outputs the escape sequence to query terminal parameters, it
808has to peek not stdin but /dev/tty. Except the cursor keys scrolling up
809and down also come from /dev/tty, as do "user hit forward slash and
810wants to type in a search regex"... Innit fun?
811
812Plus you have to figure out the width of what you're outputting, which
813means isprint() and probably UTF8 awareness... (Flashbacks to
814fontmetrics in java 1.1, but we can assume monospace text grid...)
815
816From Rob -
817----------
818
819Alas, POSIX does not seem to like simple. To start with, the spec
820requires "more" to be terminal aware, which isn't entirely surprising
821given what it does. But it's not just _height_ aware, it's width aware
822too.
823
824It's actually kinda fiddly: more needs to be able to wrap lines at the
825screen size to figure out when to prompt, but the first 32 ascii
826characters (all the stuff below space) don't consistently print one
827character. Some print nothing, some move the cursor around (tab,
828newline, linefeed, backspace, form feed, whatever the heck "vertical
829tab" does...)
830
831In theory I can hijack catv and just escape most of the low-ascii
832weirdness (so it's two characters, but it's _consistently_ two
833characters whatever $TERM thinks). In practice, when not hooked up to a
834tty more is supposed to act like cat and pass data through unmodified...
835
836I plan to do the simplest standards conformant implementation I can, and
837every once in a while I go "you know, the standard's nuts, let's just
838document the divergence"...
839
840
841From Rob -
842----------
843
844Keep in mind that Unix doesn't implement backspace sanely (like the
845commodore 64 did), thus you have to figure out when you're at the left
846edge of the screen (keeping track of your cursor position)
847
848That's why if you do "echo -n this will screw bash up" and then cursor
849up a few times bash's command history gets all wonky because it _thinks_
850it knows where the cursor is but actually started farther to the right.
851(I contributed ansi escape screen position querying code to busybox to
852improve the situation for ash.)
853
854From Rob -
855----------
856
857The question here is how much of this we already need to do for shell
858history, which is what I was going to implement first. (Actually the one
859I've already sat down and wrestled with is "more", which turns out to
860have rather a lot of these issues as well. Knowing what printable
861characters actually _print_ so you know where your darn cursor is and
862where the screen wraps. You'd think less would have more issues here,
863but more actually hits just about all of 'em...)
864
865Moving the cursor isn't the issue. Querying the screen size is a
866reasonably contained issue. Knowing how the cursor will move when you
867print out an arbitrary string: that's the hard part. (utf8 awareness is
868kinda required here. And I'm assuming a monospaced font even in klingon.
869And replacing the !isprint() characters below space with escape
870sequences...)
871
872
873From Rob and others -
874---------------------
875
876http://lists.busybox.net/pipermail/busybox/2008-October/067348.html
877
878http://lists.busybox.net/pipermail/busybox/2009-May/069314.html
879
880From Rob -
881----------
882
883Implementing ascii programming in the library itself was what I was
884referring to. (It's on my todo list...) All sorts of stuff needs it:
885more, toysh, vi... Even ls wants to know the width of the screen for -C
886mode.
887
888The problem is parsing the replies, since the user could type arbitrary
889stuff. It's stdout that needs to be a tty (because "ls | blah" is not
890going to a tty even if stdin is a tty), but the input _could_ come back
891in on stdin if that's another filehandle to the same tty... as could any
892other random input. If you're filtering all your input through a line
893reading function that needs to parse cursor keys to implement command
894history, doing this is easy. But if _all_ you care about is the probe
895response and you want to leave the rest of the input alone, it's kinda
896hard.
897
898What I might wind up doing is adding it to toysh and having that export
899COLUMNS and LINES environment variables. It wouldn't catch resizes in
900the middle of a command, but I think I'm ok with that...
901
902
903------------------------------------------------------------------------------------------------------
904
905/*
906key / mouse -> command
907menu choice -> command
908border click -> command
909popup + args -> command
910typed string -> command
911script call -> command
912script callback -> command
913C call -> command
914
915Arguments - should use the same argument defining stuff as used by the toys.
916 Keys and menus have to have fixed arguments, and take the rest from their content.
917 Popup and typed get their arguments from what the user selected in the popup, or typed.
918 Popup is told what it's widgets are and how they map to arguments.
919 Popups eventually construct a command string.
920 Script call uses generic text to call the command and set it's arguments.
921 Script callback should be similar.
922 C calls can call the functions direct, or even just pass a string command.
923
924So every command call involves it's content, and can take default arguments from that content, to be overridden by arguments.
925Keys and menus just have arguments coded into their string on definition, though might mostly be argument less.
926Popups have a structure that defines their argument widgets, and how to turn them into command arguments.
927Popups need to be created from string commands to.
928Typed commands can just have their arguments as part of the typed string.
929Scripts and callbacks just send a string that is treated the same way as typed commands.
930C can do the same as scripts, but should be able to call things directly.
931
932Sending commands back to scripts, should use the same format as our commands.
933
934Would be nice to have parameter substitution to, but that would have to be per content.
935
936Use an event system.
937
938---------------------------------------------
939
940Events
941 keystroke / mouse click
942 menu item select
943 timer
944 draw all
945 scroll contents
946 box was redrawn?
947 box destroyed
948 leave box
949 enter box
950
951void doScript(struct content *content, char commandString, struct event *event, void *blob)
952
953Define a command
954 name, argsDefinitionString, pointer to C function - void myCommand(struct box *box, char *command, struct ToyboxArguments *args, struct event *event, void *blob)
955
956Define a key
957 keyName, commandString
958
959Define a menu item
960 menuTitle, commandString
961
962Define a border widget
963 borderPosition, borderType, textOrCommand
964
965Define a popup
966 popupName, commandStub, thingThatMapsWidgetsToArgs
967
968Define a script callback
969 name, command, someKey
970
971---------------------------------------------
972
973Script interface.
974
975We might have multiple scripts running, but only one per box.
976 But what if a script wants to split it's box?
977They can't access our data structures, and we can't access theirs.
978All needs to be done via stdin/stdout plain text, which should all look like the commands in the rest of the system.
979
980Scripts can register simple callbacks on these events - key, menu, timer, box destroyed.
981The first three might want to return a damage list.
982The last means that the box wants to be destroyed, but the script gets a chance to clean up.
983
984Scripts need to be able to hook into the damage system, so there needs to be a text representation of damage areas.
985
986boxes <-> script
987
988Boxes knows which script is attached to which box.
989Registering a key for the box is optional, and it's only passed back if it exists.
990A split box gets no key, or can have the key optionally sent with the split command.
991 So how does the script deal with stuff coming from multiple boxes?
992
993<- keyForBox someKey
994<- registerKey keyName, commandString arguments
995<- registerKey keyName, callBackCommand arguments
996user hits a key
997 if the key has a command, execute it.
998 if the command is not one of ours, send it to the script instead
999-> callBackCommand arguments, someKey
1000 otherwise do our command
1001 else
1002 send the key event to the script.
1003-> handleEvent keyName, someKey
1004
1005 In any case, the script might want to change things in response.
1006 Note that it could do these at any time.
1007<- damage x, y, h, w
1008 line
1009 line
1010 line
1011 line
1012-> doneRedraw someKey
1013<- border borderPosition, borderType, textOrCommand
1014<- status line
1015
1016 *
1017 */
1018
1019
1020struct function
1021{
1022 name // Name for script purposes.
1023 description // Human name for the menus.
1024 type
1025 union
1026 {
1027 *scriptCallback
1028 *cFunction
1029 }
1030};
1031
1032struct command
1033{
1034 key // Note that any given context might have different keys for any given function.
1035 *function
1036};
1037
1038struct item
1039{
1040 type
1041 union
1042 {
1043 *command
1044 *menu
1045 }
1046}
1047
1048struct menu
1049{
1050 *items[] // Circular pointer definiton for sub menus.
1051}
1052
1053struct context // Somehow I get the feeling I'm having a failure of imagination here with the menus and function keys. Might be better to manage them seperately per box, but have common ones available? Nano might have a problem with this.
1054{
1055 *commands[] // The master list, the ones pointed to by the menu structs should be in this list.
1056 menu *menu // Can be NULL.
1057 menu *functionKeys // Can be NULL.
1058 // This can be used as the sub struct for various context types. Like viewer, editor, file browser, top, etc.
1059 // Could even be an object hierarchy, like generic editor, which Basic vi inherits from.
1060};
1061
1062char borderchars[][]
1063{
1064 // usual box graphic symbols
1065 '-|+',
1066 // ANSI code for inverse spaces.
1067}
1068
1069struct borderWidget
1070{
1071 text
1072 *clickFunction(int position)
1073}
1074
1075struct border
1076{
1077 *topLeftWidget
1078 *topRightWidget
1079 *bottomLeftWidget
1080 *bottomRightWidget
1081 *leftWidget
1082 *rightWidget
1083}
1084
1085struct damage
1086{
1087 X, Y, W, H // The rectangle to be redrawn.
1088 char **lines // Pointer to an array of text lines, or NULL.
1089 *damage // Perhaps a linked list might be in order, for fast redraws.
1090}
1091
1092struct content // For various instances of context types, in other words, the editor might have several files open, so one of these per file.
1093{
1094 minW, minH, maxW, maxH
1095 *context
1096 *handleEvents() // Should set the damage list if it needs a redraw, and flags if border or status line needs updating.
1097 // Keyboard / mouse events if the box did not handle them itself.
1098 // DrawAll event for when drawing the box for the first time, on reveals for coming out of full screen, or user hit the redraw key.
1099 // Scroll event if the content wants to handle that itself.
1100 // Timer event for things like top that might want to have this called regularly.
1101 *doneRedraw() // The box is done with it's redraw, so we can free the damage list or whatever now.
1102 *delete()
1103 // This can be used as the sub struct for various content types.
1104};
1105
1106struct contentData
1107{
1108 *border // Can be NULL.
1109 *statusLine // Text of the status line, or NULL if none.
1110 offsetX, offsetY, W, H // Offset and size within the content, coz box handles scrolling, usually.
1111 bool redrawStatus, redrawBorder
1112 *damage // Can be NULL. If not NULL after content->doneRedraw(), box will free it and it's children.
1113 void *data // The content controls this blob, it's specific to each box.
1114}
1115
1116struct box
1117{
1118 box *sub, *parent
1119 bool noBorderOnFullScreen
1120 bool horizontalSplit // Marks if it's horizontally or vertically split.
1121 splitProportion // proportion of this boxes part of the split, the sub box gets the rest.
1122 *content
1123 contentData // Data blob specific to this box, passed to each content function. For sharing contents, like a split pane editor for instance. Not a pointer, but the struct.
1124 X, Y, W, H // Position and size of the box itself, not the content. Calculated, but cached coz that might be needed for speed.
1125 cX, cY // Position of the content within the box. Calculated, but cached coz that might be needed for speed.
1126};
1127
1128box root; // Always a full screen, parent of the rest of the boxes, or the only box.
1129box current;
1130bool currentIsFullScreen;
diff --git a/src/BUGS.txt b/src/BUGS.txt
new file mode 100644
index 0000000..87c3d4b
--- /dev/null
+++ b/src/BUGS.txt
@@ -0,0 +1,6 @@
1xterm current box characters are wrong
2joe - ^K^D not working, but ^Kd is
3 Even odder, ^D works, all other ^K^? combinations work.
4F1 seems to not work, at least under xterm.
5
6should clear the command line prompt when not in use
diff --git a/src/NOTES.txt b/src/NOTES.txt
new file mode 100644
index 0000000..1758ac8
--- /dev/null
+++ b/src/NOTES.txt
@@ -0,0 +1,183 @@
1I'm re-purposing this for SledjHamr https://sledjhamr.org/git/docs/index.html
2
3The general structure of SledjHamr is a bunch of servers talking to each
4other via Internet (or just local) connections. One of them is a web
5server for assets, world data, and inventory.
6
7Originally I didn't think using a web based world client was a good idea,
8however it might be better to have one, for reasons. Now I need a web
9management console that can do all the things the current tmux console
10can, including OpenSim console and commands. Plus account management for
11users. I can also use a web based Jabber / XMPP front end to chat, IM,
12and group chatter, which would run in the normal viewers web browser.
13This provides a doorway into putting SledjHamr stuff in existing viewers
14without needing them to support it. So a web based viewer now makes more
15sense, and also means we can get away with not needing a viewer at all.
16
17Toybox itself doesn't include a web server, and I don't think there is
18one on the roadmap. So we have to use an external web server, which was
19a design goal of SledjHamr in the first place, using existing mature
20HTTP infrastructure, coz that's already solved problems for a bunch of
21things that plague OS/SL to this day. Clear your cache! Pffft.
22
23So sledjchisl.c will be the "love world server", though initially it just
24drives OpenSim_SC in tmux via tmux commands to send keys and read output.
25Later it might run opensim_SC directly and use STDIN and STDOUT to do
26everything. It'll also provide the text management front end that runs
27in the left tmux panel of the first window, which is why it's based on
28boxes in the first place. Later still it can take over opensim_SC
29functions as I move them out of mono.
30
31We will need a text, web, and GUI version of this management front end.
32Hmmm, maybe don't need a GUI version, GUI users can just run a terminal.
33
34After much research, FastCGI / FCGI seems to be the most portable way of
35interfacing with existing web servers. FCGI protocol closes STDERR and
36STDOUT, and uses STDIN as it's two way communications channel to the web
37server, so our FCGI module can't be used as the text management front
38end. This is probably a good idea to keep them seperate anyway, for
39security, coz the web server is exposed to the world, the console isn't.
40
41Currently sledjchisl.c tests to see if it's running in tmux already, if
42it isn't it starts up tmux runs itself into this new tmux, then exits.
43So it could also test if it's running from FCGI, and switch to web mode,
44then it'll need to find the tmuxed instance to send commands to it.
45Either via nails connection, or sending tmux commands via shell.
46
47FCGI has methods of dealing with auth and templates. B-)
48
49So for now I think I'll have the text and web management front ends in
50sledjchisl.c, and the love world server as well. I can split them up
51later if I need to.
52
53
54
55
56I has Apache 2.4.25-3+deb9u9
57 MariaDB 10.1.44-MariaDB
58
59
60https://gist.github.com/dermesser/e2f9b66457ae19ebd116
61 Multithreaded example in C.
62
63
64-------------------------------------------------------------------
65
66Apache doesn't seem to support FCGI filter role, so I might have to do
67without. Might be better anyway.
68
69
70"A Filter is similar in functionality to a Responder that takes a data
71file as a parameter. The difference is that with a Filter, both the data
72file and the Filter itself can be access controlled using the Web
73server's access control mechanisms, while a Responder that takes the name
74of a data file as a parameter must perform its own access control checks
75on the data file."
76
77 Which is fine, our access control checks will be "Is this database
78 defined user already logged on via our FCGI script?". We should have
79 total control over that. I was planning on using the FCGI auth
80 mechanism anyway.
81
82
83RESPONDER
84 web server sends FCGI_PARAMS
85 CONTENT_LENGTH
86 web server sends input body FCGI_STDIN
87 fcgi app sends result data over FCGI_STDOUT and error messages over FCGI_STDERR
88 it has to finish reading FCGI_PARAMS first
89 fcgi app sends FCGI_END_REQUEST(protocolStatus = FCGI_REQUEST_COMPLETE)
90
91
92FILTER
93 filtered file has last modified time
94 web server sets FCGI_DATA_LAST_MOD accordingly
95 web server sends FCGI_PARAMS
96 CONTENT_LENGTH FCGI_DATA_LAST_MOD FCGI_DATA_LENGTH
97 web server sends input body FCGI_STDIN
98 web servers sends file over FCGI_DATA
99 fcgi app can ignore FCGI_DATA and use it's own cached copy based on FCGI_DATA_LAST_MOD
100 fcgi app sends result data over FCGI_STDOUT and error messages over FCGI_STDERR
101 it has to finish reading FCGI_STDIN first, but not FCGI_DATA
102 fcgi app sends FCGI_END_REQUEST(protocolStatus = FCGI_REQUEST_COMPLETE)
103
104
105Soooo, FILTER might be slower anyway if we are caching the filtered file,
106or mmapping it, coz filter has to start sending the filtered file, even
107if it's to be served from cache. Plus no need to wait for FCGI_STDIN
108before spewing it out.
109
110
111PLAN -
112. add "webRoot" to the config, point it at /opt/opensim_SC/web
113. /opt/opensim_SC/web/fcgi-bin/sledjchisl.fcgi a symlink to /opt/opensim_SC/bin/sledjchisl
114. /opt/opensim_SC/web/html/ the static web files and templates.
115. https://localhost/opensim_SC/fcgi-bin/sledjchisl.fcgi/foo.html
116. check the if modified since bit against the replaceable bits last fetch time / /opt/opensim_SC/web/html/foo.html last modified time
117. check if /opt/opensim_SC/web/html/foo.html is cached, or if it's been modified since last cache.
118. mmap /opt/opensim_SC/web/html/foo.html
119. put it in a cache
120. scan through it looking for the replacable bits
121. store pointers to them
122. check if the replaceable bits need refreshing
123. loop through the pointers to the cache
124. spew out the non replacable bits
125. spew out the replacements for the replacable bits
126. repeat until done
127
128Last update time for parameters, plus an update frequency. Once a minute.
129Hash of page file names
130. last modified time for file
131 Linked list of page fragments -> array (qlibc can convert a linked list into an array?), or try the growable thingy.
132. struct
133. enum telling us what this bit is
134. bit of verbatim text
135. replaceable parameter, pointer to the data that is also stored in the ssi hash, so that changes propogate
136. <!--#echo var="???" -->
137 bit of Lua
138
139 NOTE - SSI is a bit more complex than what I'm currently using.
140 https://en.wikipedia.org/wiki/Server_Side_Includes
141 <!--#include virtual="menu.cgi" -->
142 <!--#include file="footer.html" -->
143 <!--#exec cgi="/cgi-bin/foo.cgi" -->
144 <!--#exec cmd="ls -l" -->
145. <!--#echo var="REMOTE_ADDR" -->
146 <!--#config timefmt="%y %m %d" -->
147 <!--#config sizefmt="bytes" -->
148 <!--#config errmsg="SSI command failed!" -->
149 <!--#flastmod virtual="index.html" -->
150 <!--#fsize file="script.pl" -->
151 <!--#if expr="${Sec_Nav}" -->
152 <!--#include virtual="secondary_nav.txt" -->
153 <!--#elif expr="${Pri_Nav}" -->
154 <!--#include virtual="primary_nav.txt" -->
155 <!--#else -->
156 <!--#include virtual="article.txt" -->
157 <!--#endif -->
158 <!--#set var="foo" value="bar" -->
159 <!--#printenv -->
160 https://www.w3.org/Jigsaw/Doc/User/SSI.html
161 Adds lots of others, including Java stuff.
162 Mine
163 <!--#lua lua="print(table[key])" -->
164 <!--#lua file="/path/to/script.lua" -->
165 <!--#lua virtual="https://example.com/script.lua" -->
166
167BTW - /opt/opensim_SC/web/html/foo.html should have it's image URLS and other
168static data set to https://localhost/opensim_SC/SledjHamr.png, which
169would map to /opt/opensim_SC/web/html/SledjHamr.png and be treated as ordinary
170static files by the web server.
171
172ALSO - when spewing results, we have to manually send the headers first
173our selves, this should include the HTTP status code and string, content
174type, cookies, etc.
175
176-------------------------------------------------------------------
177
178https://project-awesome.org/aleksandar-todorovic/awesome-c
179 A curated list of C good stuff.
180
181https://wolkykim.github.io/qdecoder/
182 CGI library made by the qlibc guy, does support FCGI.
183 Might be a wrapper around the fcgi_stdio stuff I'm already using?
diff --git a/src/README b/src/README
new file mode 100644
index 0000000..7769eec
--- /dev/null
+++ b/src/README
@@ -0,0 +1,27 @@
1Boxes is test bed for a generic editor / pager thingy for the toybox project.
2
3The toybox project is at http://www.landley.net/code/toybox/ and boxes
4is covered by the same license that toybox is. Basically that's a two
5clause BSD license, but see the LICENSE file from toybox for details.
6
7This is a work in progress, proof of concept, playground, packaged up as
8one big toy, to be self contained until the mess is cleaned up. This
9"boxes" toy itself will go away, to be replaced by individual editor /
10pager toys and library bits. Nothing is set in stone, lots of mess
11inside, there's bugs, but at least it shows the general direction my
12mind is wandering in. As a bonus, it actually works, you can edit
13stuff.
14
15Please don't actually include this in toybox. Just look at it and sneer
16/ giggle, depending on your nature. Drop it into the toys directory to
17try it out, it's just one big toy.
18
19If you want to see how it can be used to build specific editors, start
20at the end and work backwards. Reading the lengthy comments at the
21beginning would also be useful.
22
23Toybox uses mecurial instead of git, but I keep all my stuff on github.
24Boxes will hopefully be incorporated into toybox in a highly altered
25form some day in the future. So this is just a temporary git repo for
26my convenience. If and when boxes migrates to toybox, this repo will be
27retired.
diff --git a/src/boxes.c b/src/boxes.c
new file mode 100644
index 0000000..0a293b4
--- /dev/null
+++ b/src/boxes.c
@@ -0,0 +1,2607 @@
1/* boxes.c - Generic editor development sandbox.
2 *
3 * Copyright 2012 David Seikel <won_fang@yahoo.com.au>
4 *
5 * Not in SUSv4. An entirely new invention, thus no web site either.
6 * See -
7 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ex.html
8 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/more.html
9 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
10 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html
11 * http://linux.die.net/man/1/less
12
13USE_BOXES(NEWTOY(boxes, "w#h#m(mode):a(stickchars)1", TOYFLAG_USR|TOYFLAG_BIN))
14
15config BOXES
16 bool "boxes"
17 default y
18 help
19 usage: boxes [-m|--mode mode] [-a|--stickchars] [-w width] [-h height]
20
21 Generic text editor and pager.
22
23 Mode selects which editor or text viewr it emulates, the choices are -
24 emacs is a microemacs type editor.
25 joe is a joe / wordstar type editor.
26 less is a less type pager.
27 mcedit (the default) is cooledit / mcedit type editor.
28 more is a more type pager.
29 nano is a nano / pico type editor.
30 vi is a vi type editor.
31
32 Stick chars means to use ASCII for the boxes instead of "graphics" characters.
33*/
34
35
36#include "toys.h"
37#include "lib/handlekeys.h"
38
39#include <lua.h>
40#include <lualib.h>
41#include <lauxlib.h>
42
43
44
45GLOBALS(
46 char *mode;
47 long h, w;
48)
49
50#define TT this.boxes
51
52#define FLAG_a 2
53#define FLAG_m 4
54#define FLAG_h 8
55#define FLAG_w 16
56
57
58/* This is trying to be a generic text editing, text viewing, and terminal
59 * handling system. The current code is a work in progress, and the design
60 * may change. Certainly at this moment it's only partly written. It is
61 * "usable" though, for a very small value of "usable". In the following
62 * I'll use "editors" to refer to the toys using this, though not all will
63 * be editors.
64 *
65 * The things it is targeting are - vi and more (part of the standards, so
66 * part of the toybox TODO), less (also on the toybox TODO), joe and
67 * wordstar (coz Rob said they would be good to include), nano (again Rob
68 * thinks it would be good and I agree), microemacs (to avoid religous
69 * wars), and mcedit (coz that's what I actually use). The ex editor comes
70 * along for the ride coz vi is basically a screen editor wrapper around
71 * the ex line editor. Sed might be supported coz I'll need to do basic
72 * editing functions that are common to the editors, and sed needs the same
73 * editing functions.
74 *
75 * I will also use this for a midnight commander clone as discussed on the
76 * mailing list. This would have things in common with emacs dired, so we
77 * might get that as well. Parts of this code could also be used for a
78 * file chooser, as used by some of the editors we are targeting. Finally,
79 * the terminal handling stuff might be useful for other toys, so should be
80 * generic in it's own right. Oh, screen is listed in the toybox TODO as
81 * "maybe", so I'll poke at that to.
82 *
83 * The basic building blocks are box, content, context, edit line, and
84 * view. A box is an on screen rectanglur area. Content is a file, and
85 * the text that is in that file. A context represents a particular editor
86 * type, it has key mappings and other similar stuff. The edit line is a
87 * single line where editing happens, it's similar to readline. A view is
88 * a view into a content, there can be many, it represents that portion of
89 * the content that is on screen right now.
90 *
91 * I plan on splitting these things up a bit so they can be used
92 * separately. Then I can make actually toybox libraries out of them. For
93 * now it's all one big file for ease of development.
94 *
95 * The screen is split into boxes, by default there are only two, the main
96 * text area and the command line at the bottom. Each box contains a view,
97 * and each view points to a content (file) for it's text. A content can
98 * have many views. Each content has a context (editor). There is only
99 * ever one edit line, it's the line that is being edited at the moment.
100 * The edit line moves within and between boxes (including the command
101 * line) as needed.
102 *
103 * The justification for boxes is that most of the editors we are trying to
104 * emulate include some splitting up of the screen area for various
105 * reasons, and some of them include a split window system as well. So
106 * this boxes concept covers command line, main editing area, split windows,
107 * menus, on screen display of command keys, file selection, and anything
108 * else that might be needed.
109 *
110 * To keep things simple boxes are organised as a binary tree of boxes.
111 * There is a root box, it's a global. Each box can have two sub boxes.
112 * Only the leaf nodes of the box tree are visible on the screen. Each box
113 * with sub boxes is split either horizontally or vertically. Navigating
114 * through the boxes goes depth first.
115 *
116 * A content keeps track of a file and it's text. Each content also has a
117 * context, which is a collection of the things that define a particular
118 * editor. (I might move the context pointer from content to view, makes
119 * more sense I think.)
120 *
121 * A context is the heart of the generic part of the system. Any given
122 * toybox command that uses this system would basically define a context
123 * and pass that to the rest of the system. See the end of this file for a
124 * few examples. A context holds a list of command to C function mappings,
125 * key to command mappings, and a list of modes.
126 *
127 * Most of the editors targetted include a command line where the user
128 * types in editor commands, and each of those editors has different
129 * commands. They would mostly use the same editing C functions though, or
130 * short wrappers around them. The vi context at the end of this file is a
131 * good example, it has a bunch of short C wrappers around some editing
132 * functions, or uses standard C editing functions directly. So a context
133 * has an editor command to C function mapping.
134 *
135 * The editors respond to keystrokes, and those keystrokes invoke editor
136 * commands, often in a modal way. So there are keystroke to editor
137 * command mappings. To cater for editing modes, each context has a list
138 * of modes, and each mode can have key to command mappings, as well as
139 * menu to command mappings, and a list of displayed key/command pairs.
140 * Menu and key/command pair display is not written yet. Most editors have
141 * a system for remapping key to command mappings, that's not supported
142 * yet. Emacs has a heirarchy of key to command mappings, that's not
143 * supported yet. Some twiddling of the current design would be needed for
144 * those.
145 *
146 * The key mappings used can be multiple keystrokes in a sequence, the
147 * system caters for that. Some can be multi byte like function keys, and
148 * even different strings of bytes depending on the terminal type. To
149 * simplify this, there is a table that maps various terminals ideas of
150 * special keys to key names, and the mapping of keys to commands uses
151 * those key names.
152 *
153 * A view represents the on screen visible portion of a content within a
154 * box. To cater for split window style editors, a content can have many
155 * views. It deals with keeping track of what's shown on screen, mapping
156 * the on screen representation of the text to the stored text during input
157 * and output. Each box holds one view.
158 *
159 * The edit line is basically a movable readline. There are editing C
160 * functions for moving it up and down lines within a view, and for
161 * shifting the edit line to some other box. So an editor would map the
162 * cursor keys for "up a line" and "down a line" to these C functions for
163 * example. Actual readline style functionality is just the bottom command
164 * box being a single line view, with the history file loaded into it's
165 * content, and the Enter key mapped to the editor contexts "do this
166 * command" function. Using most of the system with not much special casing.
167 *
168 *
169 * I assume that there wont be a terribly large number of boxes.
170 * Things like minimum box sizes, current maximum screen sizes, and the fact
171 * that they all have to be on the screen mean that this assumption should
172 * be safe. It's likely that most of the time there will be only a few at
173 * most. The point is there is a built in limit, there's only so many non
174 * overlapping boxes with textual contents that you can squeeze onto one
175 * terminal screen at once.
176 *
177 * I assume that there will only be one command line, no matter how many boxes,
178 * and that the command line is for the current box.
179 *
180 * I use a wide screen monitor and small font.
181 * My usual terminal is 104 x 380 characters.
182 * There will be people with bigger monitors and smaller fonts.
183 * So use sixteen bits for storing screen positions and the like.
184 * Eight bits wont cut it.
185 *
186 *
187 * These are the escape sequences we send -
188 * \x1B[m reset attributes and colours
189 * \x1B[1m turn on bold
190 * \x1B[%d;%dH move cursor
191 * Plus some experimentation with turning on mouse reporting that's not
192 * currently used.
193 *
194 *
195 * TODO - disentangle boxes from views.
196 *
197 * TODO - should split this up into editing, UI, and boxes parts,
198 * so the developer can leave out bits they are not using.
199 *
200 * TODO - Show status line instead of command line when it's not being edited.
201 *
202 * TODO - should review it all for UTF8 readiness. Think I can pull that off
203 * by keeping everything on the output side as "screen position", and using
204 * the formatter to sort out the input to output mapping.
205 *
206 * TODO - see if there are any simple shortcuts to avoid recalculating
207 * everything all the time. And to avoid screen redraws.
208 */
209
210/* Robs "It's too big" lament.
211
212> So when you give me code, assume I'm dumber than you. Because in this
213> context, I am. I need bite sized pieces, each of which I can
214> understand in its entirety before moving on to the next.
215
216As I mentioned in my last email to the Aboriginal linux list, I wrote
217the large blob so I could see how the ideas all work to do the generic
218editor stuff and other things. It kinda needs to be that big to do
219anything that is actually useful. So, onto musings about cutting it up
220into bite sized bits...
221
222You mentioned on the Aboriginal Linux list that you wanted a
223"readline", and you listed some features. My reply was that you had
224basically listed the features of my "basic editor". The main
225difference between "readline" and a full screen editor, is that the
226editor is fullscreen, while the "readline" is one line. They both have
227to deal with a list of lines, going up and down through those lines,
228editing the contents of those lines, one line at a time. For persistent
229line history, they both have to save and load those lines to a file.
230Other than that "readline" adds other behaviour, like moving the
231current line to the end when you hit return and presenting that line to
232the caller (here's the command). So just making "readline" wont cut
233out much of the code. In fact, my "readline" is really just a thin
234wrapper around the rest of the code.
235
236Starting from the other end of the size spectrum, I guess "find out
237terminal size" is a small enough bite sized chunk. To actually do
238anything useful with that you would probably start to write stuff I've
239already written though. Would be better off just using the stuff I've
240already written. On the other hand, maybe that would be useful for
241"ls" listings and the like, then we can start with just that bit?
242
243I guess the smallest useful command I have in my huge blob of code
244would be "more". I could even cut it down more. Show a page of text,
245show the next page of text when you hit the space bar, quit when you
246hit "q". Even then, I would estimate it would only cut out a third of
247the code.
248
249On the other hand, there's a bunch of crap in that blob that is for
250future plans, and is not doing anything now.
251
252In the end though, breaking it up into suitable bite sized bits might
253be almost as hard as writing it in the first place. I'll see what I
254can do, when I find some time. I did want to break it up into three
255bits anyway, and there's a TODO to untangle a couple of bits that are
256currently too tightly coupled.
257
258*/
259
260/* Robs contradiction
261
262On Thu, 27 Dec 2012 06:06:53 -0600 Rob Landley <rob@landley.net> wrote:
263
264> On 12/27/2012 12:56:07 AM, David Seikel wrote:
265> > On Thu, 27 Dec 2012 00:37:46 -0600 Rob Landley <rob@landley.net>
266> > wrote:
267> > > Since ls isn't doiing any of that, probing would just be awkward
268> > > so toysh should set COLUMNS to a sane value. The problem is,
269> > > we're not using toysh yet because it's still just a stub...
270> >
271> > I got some or all of that terminal sizing stuff in my editor thingy
272> > already if I remember correctly. I remember you wanted me to cut it
273> > down into tiny bits to start with, so you don't have to digest the
274> > huge lump I sent.
275>
276> Basically what I'd like is a self-contained line reader. More or less
277> a getline variant that can handle cursoring left and right to insert
278> stuff, handle backspace past a wordwrap (which on unix requires
279> knowing the screen width and your current cursor position), and one
280> that lets up/down escape sequences be hooked for less/more screen
281> scrolling, vi line movement, shell command history, and so on.
282
283Which is most of an editor already, so how to break it up into bite
284sized morsels?
285
286> There's sort of three levels here, the first is "parse raw input,
287> assembling escape sequences into cursor-left and similar as
288> necessary". (That's the one I had to redo in busybox vi because they
289> didn't do it right.)
290>
291> The second is "edit a line of text, handling cursoring _within_ the
292> line". That's the better/interactive getline().
293>
294> The third is handling escapes from that line triggering behavior in
295> the larger command (cursor up and cursor down do different things in
296> less, in vi, and in shell command history).
297>
298> The fiddly bit is that terminal_size() sort of has to integrate with
299> all of these. Possibly I need to have width and height be members of
300> struct toy_context. Or have the better_getline() take a pointer to a
301> state structure it can update, containing that info...
302
303*/
304
305
306static char *borderChars[][6] =
307{
308 {"-", "|", "+", "+", "+", "+"}, // "stick" characters.
309 {"\xE2\x94\x80", "\xE2\x94\x82", "\xE2\x94\x8C", "\xE2\x94\x90", "\xE2\x94\x94", "\xE2\x94\x98"}, // UTF-8
310 {"\x71", "\x78", "\x6C", "\x6B", "\x6D", "\x6A"}, // VT100 alternate character set.
311 {"\xC4", "\xB3", "\xDA", "\xBF", "\xC0", "\xD9"} // DOS
312};
313
314static char *borderCharsCurrent[][6] =
315{
316 {"=", "#", "+", "+", "+", "+"}, // "stick" characters.
317 {"\xE2\x95\x90", "\xE2\x95\x91", "\xE2\x95\x94", "\xE2\x95\x97", "\xE2\x95\x9A", "\xE2\x95\x9D"}, // UTF-8
318 {"\x71", "\x78", "\x6C", "\x6B", "\x6D", "\x6A"}, // VT100 alternate character set has none of these. B-(
319 {"\xCD", "\xBA", "\xC9", "\xBB", "\xC8", "\xBC"} // DOS
320};
321
322
323typedef struct _box box;
324typedef struct _view view;
325
326typedef void (*boxFunction) (box *box);
327typedef void (*eventHandler) (view *view);
328
329struct function
330{
331 char *name; // Name for script purposes.
332 char *description; // Human name for the menus.
333 char type;
334 union
335 {
336 eventHandler handler;
337 char *scriptCallback;
338 };
339};
340
341struct keyCommand
342{
343 char *key, *command;
344};
345
346struct item
347{
348 char *text; // What's shown to humans.
349 struct key *key; // Shortcut key while the menu is displayed.
350 // If there happens to be a key bound to the same command, the menu system should find that and show it to.
351 char type;
352 union
353 {
354 char *command;
355 struct item *items; // An array of next level menu items.
356 };
357};
358
359struct borderWidget
360{
361 char *text, *command;
362};
363
364// TODO - a generic "part of text", and what is used to define them.
365// For instance - word, line, paragraph, section.
366// Each context can have a collection of these.
367
368struct mode
369{
370 struct keyCommand *keys; // An array of key to command mappings.
371 struct item *items; // An array of top level menu items.
372 struct item *functionKeys; // An array of single level "menus". Used to show key commands.
373 uint8_t flags; // commandMode.
374};
375
376/*
377Have a common menu up the top.
378 MC has a menu that changes per mode.
379 Nano has no menu.
380Have a common display of certain keys down the bottom.
381 MC is one row of F1 to F10, but changes for edit / view / file browse. But those are contexts here.
382 Nano is 12 random Ctrl keys, possibly in two lines, that changes depending on the editor mode, like editing the prompt line for various reasons, help.
383*/
384struct context // Defines a context for content. Text viewer, editor, file browser for instance.
385{
386 struct function *commands; // The master list, the ones pointed to by the menus etc should be in this list.
387 struct mode *modes; // A possible empty array of modes, indexed by the view.
388 // OR might be better to have these as a linked list, so that things like Emacs can have it's mode keymap hierarcy.
389 eventHandler handler; // TODO - Might be better to put this in the modes. I think vi will need that.
390 // Should set the damage list if it needs a redraw, and flags if border or status line needs updating.
391 // Keyboard / mouse events if the box did not handle them itself.
392 // DrawAll event for when drawing the box for the first time, on reveals for coming out of full screen, or user hit the redraw key.
393 // Scroll event if the content wants to handle that itself.
394 // Timer event for things like top that might want to have this called regularly.
395 boxFunction doneRedraw; // The box is done with it's redraw, so we can free the damage list or whatever now.
396 boxFunction delete;
397 // This can be used as the sub struct for various context types. Like viewer, editor, file browser, top, etc.
398 // Could even be an object hierarchy, like generic editor, which Basic vi inherits from.
399 // Or not, since the commands might be different / more of them.
400};
401
402// TODO - might be better off just having a general purpose "widget" which includes details of where it gets attached.
403// Status lines can have them to.
404struct border
405{
406 struct borderWidget *topLeft, *topMiddle, *topRight;
407 struct borderWidget *bottomLeft, *bottomMiddle, *bottomRight;
408 struct borderWidget *left, *right;
409};
410
411struct line
412{
413 struct line *next, *prev;
414 uint32_t length; // Careful, this is the length of the allocated memory for real lines, but the number of lines in the header node.
415 char *line; // Should be blank for the header.
416};
417
418struct damage
419{
420 struct damage *next; // A list for faster draws?
421 uint16_t X, Y, W, H; // The rectangle to be redrawn.
422 uint16_t offset; // Offest from the left for showing lines.
423 struct line *lines; // Pointer to a list of text lines, or NULL.
424 // Note - likely a pointer into the middle of the line list in a content.
425};
426
427struct content // For various instances of context types.
428 // Editor / text viewer might have several files open, so one of these per file.
429 // MC might have several directories open, one of these per directory. No idea why you might want to do this. lol
430{
431 struct context *context;
432 char *name, *file, *path;
433 struct line lines;
434// file type
435// double linked list of bookmarks, pointer to line, character position, length (or ending position?), type, blob for types to keep context.
436 uint16_t minW, minH, maxW, maxH;
437 uint8_t flags; // readOnly, modified.
438 // This can be used as the sub struct for various content types.
439};
440
441struct _view
442{
443 struct content *content;
444 box *box;
445 struct border *border; // Can be NULL.
446 char *statusLine; // Text of the status line, or NULL if none.
447 int mode; // For those contexts that can be modal. Normally used to index the keys, menus, and key displays.
448 struct damage *damage; // Can be NULL. If not NULL after context->doneRedraw(), box will free it and it's children.
449 // TODO - Gotta be careful of overlapping views.
450 void *data; // The context controls this blob, it's specific to each box.
451 uint32_t offsetX, offsetY; // Offset within the content, coz box handles scrolling, usually.
452 uint16_t X, Y, W, H; // Position and size of the content area within the box. Calculated, but cached coz that might be needed for speed.
453 uint16_t cX, cY; // Cursor position within the content.
454 uint16_t iX, oW; // Cursor position inside the lines input text, in case the formatter makes it different, and output length.
455 char *output; // The current line formatted for output.
456 uint8_t flags; // redrawStatus, redrawBorder;
457
458 // Assumption is that each box has it's own editLine (likely the line being edited), or that there's a box that is just the editLine (emacs minibuffer, and command lines for other proggies).
459 struct line *line; // Pointer to the current line, might be the only line.
460 char *prompt; // Optional prompt for the editLine.
461
462// Display mode / format hook.
463// view specific bookmarks, including highlighted block and it's type.
464// Linked list of selected lines for a filtered view, or processing only those lines.
465// Linked list of pointers to struct keyCommand, for emacs keymaps hierarchy, and anything similar in other editors. Plus some way of dealing with emacs minor mode keymaps.
466};
467
468struct _box
469{
470 box *sub1, *sub2, *parent;
471 view *view; // This boxes view into it's content. For sharing contents, like a split pane editor for instance, there might be more than one box with this content, but a different view.
472 // If it's just a parent box, it wont have this, so just make it a damn pointer, that's the simplest thing. lol
473 // TODO - Are parent boxes getting a view anyway?
474 uint16_t X, Y, W, H; // Position and size of the box itself, not the content. Calculated, but cached coz that might be needed for speed.
475 float split; // Ratio of sub1's part of the split, the sub2 box gets the rest.
476 uint8_t flags; // Various flags.
477};
478
479
480// Sometimes you just can't avoid circular definitions.
481void drawBox(box *box);
482
483
484#define BOX_HSPLIT 1 // Marks if it's a horizontally or vertically split.
485#define BOX_BORDER 2 // Mark if it has a border, often full screen boxes wont.
486
487static int overWriteMode;
488static box *rootBox; // Parent of the rest of the boxes, or the only box. Always a full screen.
489static box *currentBox;
490static view *commandLine;
491static int commandMode;
492
493#define MEM_SIZE 128 // Chunk size for line memory allocation.
494
495// Inserts the line after the given line, or at the end of content if no line.
496struct line *addLine(struct content *content, struct line *line, char *text, uint32_t length)
497{
498 struct line *result = NULL;
499 uint32_t len;
500
501 if (!length)
502 length = strlen(text);
503 // Round length up.
504 len = (((length + 1) / MEM_SIZE) + 1) * MEM_SIZE;
505 result = xzalloc(sizeof(struct line));
506 result->line = xzalloc(len);
507 result->length = len;
508 strncpy(result->line, text, length);
509
510 if (content)
511 {
512 if (!line)
513 line = content->lines.prev;
514
515 result->next = line->next;
516 result->prev = line;
517
518 line->next->prev = result;
519 line->next = result;
520
521 content->lines.length++;
522 }
523 else
524 {
525 result->next = result;
526 result->prev = result;
527 }
528
529 return result;
530}
531
532void freeLine(struct content *content, struct line *line)
533{
534 line->next->prev = line->prev;
535 line->prev->next = line->next;
536 if (content)
537 content->lines.length--;
538 free(line->line);
539 free(line);
540}
541
542void loadFile(struct content *content)
543{
544 int fd = open(content->path, O_RDONLY);
545
546 if (-1 != fd)
547 {
548 char *temp = NULL;
549 long len;
550
551 do
552 {
553 // TODO - get_line() is slow, and wont help much with DOS and Mac line endings.
554 temp = get_line(fd);
555 if (temp)
556 addLine(content, NULL, temp, len);
557 } while (temp);
558 close(fd);
559 }
560}
561
562// TODO - load and save should be able to deal with pipes, and with loading only parts of files, to load more parts later.
563
564void saveFile(struct content *content)
565{
566// TODO - Should do "Save as" as well. Which is just a matter of changing content->path before calling this.
567 int fd;
568
569 fd = open(content->path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
570
571 if (-1 != fd)
572 {
573 struct line *line = content->lines.next;
574
575 while (&(content->lines) != line) // We are at the end if we have wrapped to the beginning.
576 {
577 write(fd, line->line, strlen(line->line));
578 write(fd, "\n", 1);
579 line = line->next;
580 }
581 close(fd);
582 }
583 else
584 {
585 fprintf(stderr, "Can't open file %s\n", content->path);
586 exit(1);
587 }
588}
589
590struct content *addContent(char *name, struct context *context, char *filePath)
591{
592 struct content *result = xzalloc(sizeof(struct content));
593
594 result->lines.next = &(result->lines);
595 result->lines.prev = &(result->lines);
596 result->name = strdup(name);
597 result->context = context;
598
599 if (filePath)
600 {
601 result->path = strdup(filePath);
602 loadFile(result);
603 }
604
605 return result;
606}
607
608// General purpose line moosher. Used for appends, inserts, overwrites, and deletes.
609// TODO - should have the same semantics as mooshStrings, only it deals with whole lines in double linked lists.
610// We need content so we can adjust it's number of lines if needed.
611void mooshLines(struct content *content, struct line *result, struct line *moosh, uint16_t index, uint16_t length, int insert)
612{
613}
614
615// General purpose string moosher. Used for appends, inserts, overwrites, and deletes.
616void mooshStrings(struct line *result, char *moosh, uint16_t index, uint16_t length, int insert)
617{
618 char *c, *pos;
619 int limit = strlen(result->line);
620 int mooshLen = 0, resultLen;
621
622 if (moosh)
623 mooshLen = strlen(moosh);
624
625 /*
626 * moosh == NULL a deletion
627 * length == 0 simple insertion
628 * length < mooshlen delete some, insert moosh
629 * length == mooshlen exact overwrite.
630 * length > mooshlen delete a lot, insert moosh
631 */
632
633 mooshLen -= length;
634 resultLen = limit + mooshLen;
635
636 // If we need more space, allocate more.
637 if (resultLen > result->length)
638 {
639 result->length = resultLen + MEM_SIZE;
640 result->line = xrealloc(result->line, result->length);
641 }
642
643 if (limit <= index) // At end, just add to end.
644 {
645 // TODO - Possibly add spaces to pad out to where index is?
646 // Would be needed for "go beyond end of line" and "column blocks".
647 // Both of those are advanced editing.
648 index = limit;
649 insert = 1;
650 }
651
652 pos = &(result->line[index]);
653
654 if (insert) // Insert / delete before current character, so move it and the rest up / down mooshLen bytes.
655 {
656 if (0 < mooshLen) // Gotta move things up.
657 {
658 c = &(result->line[limit]);
659 while (c >= pos)
660 {
661 *(c + mooshLen) = *c;
662 c--;
663 }
664 }
665 else if (0 > mooshLen) // Gotta move things down.
666 {
667 c = pos;
668 while (*c)
669 {
670 *c = *(c - mooshLen); // A double negative.
671 c++;
672 }
673 }
674 }
675
676 if (moosh)
677 {
678 c = moosh;
679 do
680 {
681 *pos++ = *c++;
682 }
683 while (*c);
684 }
685}
686
687// TODO - Should draw the current border in green, the text as default (or highlight / bright).
688// Then allow one other box to be red bordered (MC / dired destination box).
689// All other boxes with dark gray border, and dim text.
690void drawLine(int y, int start, int end, char *left, char *internal, char *contents, char *right, int current)
691{
692 int size = strlen(internal);
693 int len = (end - start) * size, x = 0;
694 char line[len + 1];
695
696 if ('\0' != left[0]) // Assumes that if one side has a border, then so does the other.
697 len -= 2 * size;
698
699 if (contents)
700 {
701 // strncpy wont add a null at the end if the source is longer, but will pad with nulls if source is shorter.
702 // So it's best to put a safety null in first.
703 line[len] = '\0';
704 strncpy(line, contents, len);
705 // Make sure the following while loop pads out line with the internal character.
706 x = strlen(line);
707 }
708 while (x < len)
709 {
710 strcpy(&line[x], internal);
711 x += size;
712 }
713 line[x++] = '\0';
714 if ('\0' == left[0]) // Assumes that if one side has a border, then so does the other.
715 {
716 if (current)
717 printf("\x1B[1m\x1B[%d;%dH%s\x1B[m", y + 1, start + 1, line);
718 else
719 printf("\x1B[m\x1B[%d;%dH%s", y + 1, start + 1, line);
720 }
721 else
722 {
723 if (current)
724 printf("\x1B[1m\x1B[%d;%dH%s%s%s\x1B[m", y + 1, start + 1, left, line, right);
725 else
726 printf("\x1B[m\x1B[%d;%dH%s%s%s", y + 1, start + 1, left, line, right);
727 }
728}
729
730void formatCheckCursor(view *view, long *cX, long *cY, char *input)
731{
732 int i = 0, o = 0, direction = (*cX) - view->cX;
733
734 // Adjust the cursor if needed, depending on the contents of the line, and the direction of travel.
735 while (input[i])
736 {
737 // When o is equal to the cX position, update the iX position to be where i is.
738 if ('\t' == input[i])
739 {
740 int j = 8 - (i % 8);
741
742 // Check if the cursor is in the middle of the tab.
743 if (((*cX) > o) && ((*cX) < (o + j)))
744 {
745 if (0 <= direction)
746 {
747 *cX = (o + j);
748 view->iX = i + 1;
749 }
750 else
751 {
752 *cX = o;
753 view->iX = i;
754 }
755 }
756 o += j;
757 }
758 else
759 {
760 if ((*cX) == o)
761 view->iX = i;
762 o++;
763 }
764 i++;
765 }
766 // One more check in case the cursor is at the end of the line.
767 if ((*cX) == o)
768 view->iX = i;
769}
770
771// TODO - Should convert control characters to reverse video, and deal with UTF8.
772
773/* FIXME - We get passed a NULL input, apparently when the file length is close to the screen length. -
774> On Thu, 6 Sep 2012 11:56:17 +0800 Roy Tam <roytam@gmail.com> wrote:
775>
776> > 2012/9/6 David Seikel <onefang@gmail.com>:
777> > >> Program received signal SIGSEGV, Segmentation fault.
778> > >> formatLine (view=0x8069168, input=0x0, output=0x806919c)
779> > >> at toys/other/boxes.c:843
780> > >> 843 int len = strlen(input), i = 0, o = 0;
781> > >> (gdb) bt
782> > >> #0 formatLine (view=0x8069168, input=0x0, output=0x806919c)
783> > >> at toys/other/boxes.c:843
784> > >> #1 0x0804f1dd in moveCursorAbsolute (view=0x8069168, cX=0,
785> > >> cY=10, sX=0, sY=0) at toys/other/boxes.c:951
786> > >> #2 0x0804f367 in moveCursorRelative (view=0x8069168, cX=0,
787> > >> cY=10, sX=0, sY=0) at toys/other/boxes.c:1011
788> > >> #3 0x0804f479 in upLine (view=0x8069168, event=0x0) at
789> > >> toys/other/boxes.c:1442 #4 0x0804fb63 in handleKey
790> > >> (view=0x8069168, i=2, keyName=<optimized out>, buffer=0xbffffad8
791> > >> "\033[A") at toys/other/boxes.c:1593 #5 0x0805008d in editLine
792> > >> (view=0x8069168, X=-1, Y=-1, W=-1, H=-1) at
793> > >> toys/other/boxes.c:1785 #6 0x08050288 in boxes_main () at
794> > >> toys/other/boxes.c:2482 #7 0x0804b262 in toy_exec
795> > >> (argv=0xbffffd58) at main.c:104 #8 0x0804b29d in toybox_main ()
796> > >> at main.c:118 #9 0x0804b262 in toy_exec (argv=0xbffffd54) at
797> > >> main.c:104 #10 0x0804b29d in toybox_main () at main.c:118
798> > >> #11 0x0804affa in main (argc=5, argv=0xbffffd54) at main.c:159
799> > >
800> > > No segfault here when I try that, nor with different files. As I
801> > > said, it has bugs. I have seen other cases before when NULL lines
802> > > are passed around near that code. Guess I've not got them all.
803> > > Might help to mention what terminal proggy you are using, it's
804> > > size in characters, toybox version, OS, etc. Something is
805> > > different between you and me.
806> >
807> > Terminal Program: PuTTY
808> > Windows size: 80 * 25 chars
809> > Toybox version: hg head
810> > OS: Linux 3.2 (Debian wheezy)
811> > File: Toybox LICENSE file
812>
813> I'll install PuTTY, try toybox hg head, and play with them later, see
814> if I can reproduce that segfault. I use the last released tarball of
815> toybox, an old Ubuntu, and a bunch of other terminal proggies. I did
816> not know that PuTTY had been ported to Unix.
817>
818> But as I mentioned, not interested in a bug hunt right now. That will
819> be more appropriate when it's less of a "sandbox for testing the
820> ideas" and more of a "good enough to bother using". Always good to
821> have other terminals for testing though.
822
823Seems to be sensitive to the number of lines. On my usual huge
824roxterm, using either toybox 0.4 or hg head, doing this -
825
826./toybox boxes -m less LICENSE -h 25
827
828and just hitting down arrow until you get to the bottom of the page
829segfaults as well. -h means "pretend the terminal is that many lines
830high", there's a similar -w as well. 80x25 in any other terminal did
831the same. For that particular file (17 lines long), any value of -h
832between 19 and 34 will segfault after some moving around. -h 35 makes
833it segfault straight away. Less than 19 or over 35 is fine. Longer
834files don't have that problem, though I suspect it will happen on
835shortish files where the number of lines is about the length of the
836screen.
837
838I guess that somewhere I'm doing maths wrong and getting an out of
839bounds line number when the file length is close to the screen length.
840I'll make note of that, and fix it when I next get time to beat on
841boxes.
842
843Thanks for reporting it Roy.
844*/
845
846int formatLine(view *view, char *input, char **output)
847{
848 int len = strlen(input), i = 0, o = 0;
849
850 *output = xrealloc(*output, len + 1);
851
852 while (input[i])
853 {
854 if ('\t' == input[i])
855 {
856 int j = 8 - (i % 8);
857
858 *output = xrealloc(*output, len + j + 1);
859 for (; j; j--)
860 {
861 (*output)[o++] = ' ';
862 len++;
863 }
864 len--; // Not counting the actual tab character itself.
865 }
866 else
867 (*output)[o++] = input[i];
868 i++;
869 }
870 (*output)[o++] = '\0';
871
872 return len;
873}
874
875void drawContentLine(view *view, int y, int start, int end, char *left, char *internal, char *contents, char *right, int current)
876{
877 char *temp = NULL;
878 int offset = view->offsetX, len;
879
880 if (contents == view->line->line)
881 {
882 view->oW = formatLine(view, contents, &(view->output));
883 temp = view->output;
884 len = view->oW;
885 }
886 else // Only time we are not drawing the current line, and only used for drawing the entire page.
887 len = formatLine(NULL, contents, &(temp));
888
889 if (offset > len)
890 offset = len;
891 drawLine(y, start, end, left, internal, &(temp[offset]), right, current);
892}
893
894void updateLine(view *view)
895{
896 int y, len;
897
898 // Coz things might change out from under us, find the current view. Again.
899 if (commandMode) view = commandLine;
900 else view = currentBox->view;
901
902 // TODO - When doing scripts and such, might want to turn off the line update until finished.
903 // Draw the prompt and the current line.
904 y = view->Y + (view->cY - view->offsetY);
905 len = strlen(view->prompt);
906 drawLine(y, view->X, view->X + view->W, "", " ", view->prompt, "", 0);
907 drawContentLine(view, y, view->X + len, view->X + view->W, "", " ", view->line->line, "", 1);
908 // Move the cursor.
909 printf("\x1B[%d;%dH", y + 1, view->X + len + (view->cX - view->offsetX) + 1);
910 fflush(stdout);
911}
912
913void doCommand(view *view, char *command)
914{
915 if (command)
916 {
917 struct function *functions = view->content->context->commands;
918 int i;
919
920// TODO - Some editors have a shortcut command concept. The smallest unique first part of each command will match, as well as anything longer.
921// A further complication is if we are not implementing some commands that might change what is "shortest unique prefix".
922
923 for (i = 0; functions[i].name; i++)
924 {
925 if (strcmp(functions[i].name, command) == 0)
926 {
927 if (functions[i].handler)
928 {
929 functions[i].handler(view);
930 updateLine(view);
931 }
932 break;
933 }
934 }
935 }
936}
937
938int moveCursorAbsolute(view *view, long cX, long cY, long sX, long sY)
939{
940 struct line *newLine = view->line;
941 long oX = view->offsetX, oY = view->offsetY;
942 long lX = view->oW, lY = view->content->lines.length - 1;
943 long nY = view->cY;
944 uint16_t w = view->W - 1, h = view->H - 1;
945 int moved = 0, updatedY = 0, endOfLine = 0;
946
947 // Check if it's still within the contents.
948 if (0 > cY) // Trying to move before the beginning of the content.
949 cY = 0;
950 else if (lY < cY) // Trying to move beyond end of the content.
951 cY = lY;
952 if (0 > cX) // Trying to move before the beginning of the line.
953 {
954 // See if we can move to the end of the previous line.
955 if (view->line->prev != &(view->content->lines))
956 {
957 cY--;
958 endOfLine = 1;
959 }
960 else
961 cX = 0;
962 }
963 else if (lX < cX) // Trying to move beyond end of line.
964 {
965 // See if we can move to the begining of the next line.
966 if (view->line->next != &(view->content->lines))
967 {
968 cY++;
969 cX = 0;
970 }
971 else
972 cX = lX;
973 }
974
975 // Find the new line.
976 while (nY != cY)
977 {
978 updatedY = 1;
979 if (nY < cY)
980 {
981 newLine = newLine->next;
982 nY++;
983 if (view->content->lines.prev == newLine) // We are at the end if we have wrapped to the beginning.
984 break;
985 }
986 else
987 {
988 newLine = newLine->prev;
989 nY--;
990 if (view->content->lines.next == newLine) // We are at the end if we have wrapped to the beginning.
991 break;
992 }
993 }
994 cY = nY;
995
996 // Check if we have moved past the end of the new line.
997 if (updatedY)
998 {
999 // Format it.
1000 view->oW = formatLine(view, newLine->line, &(view->output));
1001 if (view->oW < cX)
1002 endOfLine = 1;
1003 if (endOfLine)
1004 cX = view->oW;
1005 }
1006
1007 // Let the formatter decide if it wants to adjust things.
1008 // It's up to the formatter to deal with things if it changes cY.
1009 // On the other hand, changing cX is it's job.
1010 formatCheckCursor(view, &cX, &cY, newLine->line);
1011
1012 // Check the scrolling.
1013 lY -= view->H - 1;
1014 oX += sX;
1015 oY += sY;
1016
1017 if (oY > cY) // Trying to move above the box.
1018 oY += cY - oY;
1019 else if ((oY + h) < cY) // Trying to move below the box
1020 oY += cY - (oY + h);
1021 if (oX > cX) // Trying to move to the left of the box.
1022 oX += cX - oX;
1023 else if ((oX + w) <= cX) // Trying to move to the right of the box.
1024 oX += cX - (oX + w);
1025
1026 if (oY < 0)
1027 oY = 0;
1028 if (oY >= lY)
1029 oY = lY;
1030 if (oX < 0)
1031 oX = 0;
1032 // TODO - Should limit oX to less than the longest line, minus box width.
1033 // Gonna be a pain figuring out what the longest line is.
1034 // On the other hand, don't think that will be an actual issue unless "move past end of line" is enabled, and that's an advanced editor thing.
1035 // Though still might want to do that for the longest line on the new page to be.
1036
1037 if ((view->cX != cX) || (view->cY != cY))
1038 moved = 1;
1039
1040 // Actually update stuff, though some have been done already.
1041 view->cX = cX;
1042 view->cY = cY;
1043 view->line = newLine;
1044
1045 // Handle scrolling.
1046 if ((view->offsetX != oX) || (view->offsetY != oY))
1047 {
1048 view->offsetX = oX;
1049 view->offsetY = oY;
1050
1051 if (view->box)
1052 drawBox(view->box);
1053 }
1054
1055 return moved;
1056}
1057
1058int moveCursorRelative(view *view, long cX, long cY, long sX, long sY)
1059{
1060 return moveCursorAbsolute(view, view->cX + cX, view->cY + cY, sX, sY);
1061}
1062
1063void sizeViewToBox(box *box, int X, int Y, int W, int H)
1064{
1065 uint16_t one = 1, two = 2;
1066
1067 if (!(box->flags & BOX_BORDER))
1068 {
1069 one = 0;
1070 two = 0;
1071 }
1072 box->view->X = X;
1073 box->view->Y = Y;
1074 box->view->W = W;
1075 box->view->H = H;
1076 if (0 > X) box->view->X = box->X + one;
1077 if (0 > Y) box->view->Y = box->Y + one;
1078 if (0 > W) box->view->W = box->W - two;
1079 if (0 > H) box->view->H = box->H - two;
1080}
1081
1082view *addView(char *name, struct context *context, char *filePath, uint16_t X, uint16_t Y, uint16_t W, uint16_t H)
1083{
1084 view *result = xzalloc(sizeof(struct _view));
1085
1086 result->X = X;
1087 result->Y = Y;
1088 result->W = W;
1089 result->H = H;
1090
1091 result->content = addContent(name, context, filePath);
1092 result->prompt = xzalloc(1);
1093 // If there was content, format it's first line as usual, otherwise create an empty first line.
1094 if (result->content->lines.next != &(result->content->lines))
1095 {
1096 result->line = result->content->lines.next;
1097 result->oW = formatLine(result, result->line->line, &(result->output));
1098 }
1099 else
1100 {
1101 result->line = addLine(result->content, NULL, "\0", 0);
1102 result->output = xzalloc(1);
1103 }
1104
1105 return result;
1106}
1107
1108box *addBox(char *name, struct context *context, char *filePath, uint16_t X, uint16_t Y, uint16_t W, uint16_t H)
1109{
1110 box *result = xzalloc(sizeof(struct _box));
1111
1112 result->X = X;
1113 result->Y = Y;
1114 result->W = W;
1115 result->H = H;
1116 result->view = addView(name, context, filePath, X, Y, W, H);
1117 result->view->box = result;
1118 sizeViewToBox(result, X, Y, W, H);
1119
1120 return result;
1121}
1122
1123void freeBox(box *box)
1124{
1125 if (box)
1126 {
1127 freeBox(box->sub1);
1128 freeBox(box->sub2);
1129 if (box->view)
1130 {
1131 // In theory the line should not be part of the content if there is no content, so we should free it.
1132 if (!box->view->content)
1133 freeLine(NULL, box->view->line);
1134 free(box->view->prompt);
1135 free(box->view->output);
1136 free(box->view);
1137 }
1138 free(box);
1139 }
1140}
1141
1142void drawBox(box *box)
1143{
1144 // This could be heavily optimized, but let's keep things simple for now.
1145 // Optimized for sending less characters I mean, on slow serial links for instance.
1146
1147 char **bchars = (toys.optflags & FLAG_a) ? borderChars[0] : borderChars[1];
1148 char *left = "\0", *right = "\0";
1149 struct line *lines = NULL;
1150 int y = box->Y, current = (box == currentBox);
1151 uint16_t h = box->Y + box->H;
1152
1153 if (current)
1154 bchars = (toys.optflags & FLAG_a) ? borderCharsCurrent[0] : borderCharsCurrent[1];
1155
1156 // Slow and laborious way to figure out where in the linked list of lines we start from.
1157 // Wont scale well, but is simple.
1158 if (box->view && box->view->content)
1159 {
1160 int i = box->view->offsetY;
1161
1162 lines = &(box->view->content->lines);
1163 while (i--)
1164 {
1165 lines = lines->next;
1166 if (&(box->view->content->lines) == lines) // We are at the end if we have wrapped to the beginning.
1167 break;
1168 }
1169 }
1170
1171 if (box->flags & BOX_BORDER)
1172 {
1173 h--;
1174 left = right = bchars[1];
1175 drawLine(y++, box->X, box->X + box->W, bchars[2], bchars[0], NULL, bchars[3], current);
1176 }
1177
1178 while (y < h)
1179 {
1180 char *line = "";
1181
1182 if (lines)
1183 {
1184 lines = lines->next;
1185 if (&(box->view->content->lines) == lines) // We are at the end if we have wrapped to the beginning.
1186 lines = NULL;
1187 else
1188 line = lines->line;
1189 // Figure out which line is our current line while we are here.
1190 if (box->view->Y + (box->view->cY - box->view->offsetY) == y)
1191 box->view->line = lines;
1192 }
1193 drawContentLine(box->view, y++, box->X, box->X + box->W, left, " ", line, right, current);
1194 }
1195 if (box->flags & BOX_BORDER)
1196 drawLine(y++, box->X, box->X + box->W, bchars[4], bchars[0], NULL, bchars[5], current);
1197 fflush(stdout);
1198}
1199
1200void drawBoxes(box *box)
1201{
1202 if (box->sub1) // If there's one sub box, there's always two.
1203 {
1204 drawBoxes(box->sub1);
1205 drawBoxes(box->sub2);
1206 }
1207 else
1208 drawBox(box);
1209}
1210
1211void calcBoxes(box *box)
1212{
1213 if (box->sub1) // If there's one sub box, there's always two.
1214 {
1215 box->sub1->X = box->X;
1216 box->sub1->Y = box->Y;
1217 box->sub1->W = box->W;
1218 box->sub1->H = box->H;
1219 box->sub2->X = box->X;
1220 box->sub2->Y = box->Y;
1221 box->sub2->W = box->W;
1222 box->sub2->H = box->H;
1223 if (box->flags & BOX_HSPLIT)
1224 {
1225 box->sub1->H *= box->split;
1226 box->sub2->H -= box->sub1->H;
1227 box->sub2->Y += box->sub1->H;
1228 }
1229 else
1230 {
1231 box->sub1->W *= box->split;
1232 box->sub2->W -= box->sub1->W;
1233 box->sub2->X += box->sub1->W;
1234 }
1235 sizeViewToBox(box->sub1, -1, -1, -1, -1);
1236 calcBoxes(box->sub1);
1237 sizeViewToBox(box->sub2, -1, -1, -1, -1);
1238 calcBoxes(box->sub2);
1239 }
1240 // Move the cursor to where it is, to check it's not now outside the box.
1241 moveCursorAbsolute(box->view, box->view->cX, box->view->cY, 0, 0);
1242
1243 // We could call drawBoxes() here, but this is recursive, and so is drawBoxes().
1244 // The combination might be deadly. Drawing the content of a box might be an expensive operation.
1245 // Later we might use a dirty box flag to deal with this, if it's not too much of a complication.
1246}
1247
1248void deleteBox(view *view)
1249{
1250 box *box = view->box;
1251
1252 if (box->parent)
1253 {
1254 struct _box *oldBox = box, *otherBox = box->parent->sub1;
1255
1256 if (otherBox == oldBox)
1257 otherBox = box->parent->sub2;
1258 if (currentBox->parent == box->parent)
1259 currentBox = box->parent;
1260 box = box->parent;
1261 box->X = box->sub1->X;
1262 box->Y = box->sub1->Y;
1263 if (box->flags & BOX_HSPLIT)
1264 box->H = box->sub1->H + box->sub2->H;
1265 else
1266 box->W = box->sub1->W + box->sub2->W;
1267 box->flags &= ~BOX_HSPLIT;
1268 // Move the other sub boxes contents up to this box.
1269 box->sub1 = otherBox->sub1;
1270 box->sub2 = otherBox->sub2;
1271 if (box->sub1)
1272 {
1273 box->sub1->parent = box;
1274 box->sub2->parent = box;
1275 box->flags = otherBox->flags;
1276 if (currentBox == box)
1277 currentBox = box->sub1;
1278 }
1279 else
1280 {
1281 if (!box->parent)
1282 box->flags &= ~BOX_BORDER;
1283 box->split = 1.0;
1284 }
1285 otherBox->sub1 = NULL;
1286 otherBox->sub2 = NULL;
1287 // Safe to free the boxes now that we have all their contents.
1288 freeBox(otherBox);
1289 freeBox(oldBox);
1290 }
1291 // Otherwise it must be a single full screen box. Never delete that one, unless we are quitting.
1292
1293 // Start the recursive recalculation of all the sub boxes.
1294 calcBoxes(box);
1295 drawBoxes(box);
1296}
1297
1298void cloneBox(struct _box *box, struct _box *sub)
1299{
1300 sub->parent = box;
1301 // Only a full screen box has no border.
1302 sub->flags |= BOX_BORDER;
1303 sub->view = xmalloc(sizeof(struct _view));
1304 // TODO - After this is more stable, should check if the memcpy() is simpler than - xzalloc() then copy a few things manually.
1305 // Might even be able to arrange the structure so we can memcpy just part of it, leaving the rest blank.
1306 memcpy(sub->view, box->view, sizeof(struct _view));
1307 sub->view->damage = NULL;
1308 sub->view->data = NULL;
1309 sub->view->output = NULL;
1310 sub->view->box = sub;
1311 if (box->view->prompt)
1312 sub->view->prompt = strdup(box->view->prompt);
1313}
1314
1315void splitBox(box *box, float split)
1316{
1317 uint16_t size;
1318 int otherBox = 0;
1319
1320 // First some sanity checks.
1321 if (0.0 > split)
1322 {
1323 // TODO - put this in the status line, or just silently fail. Also, better message. lol
1324 fprintf(stderr, "User is crazy.\n");
1325 return;
1326 }
1327 else if (1.0 <= split) // User meant to unsplit, and it may already be split.
1328 {
1329 // Actually, this means that the OTHER sub box gets deleted.
1330 if (box->parent)
1331 {
1332 if (box == box->parent->sub1)
1333 deleteBox(box->parent->sub2->view);
1334 else
1335 deleteBox(box->parent->sub1->view);
1336 }
1337 return;
1338 }
1339 else if (0.0 < split) // This is the normal case, so do nothing.
1340 {
1341 }
1342 else // User meant to delete this, zero split.
1343 {
1344 deleteBox(box->view);
1345 return;
1346 }
1347 if (box->flags & BOX_HSPLIT)
1348 size = box->H;
1349 else
1350 size = box->W;
1351 if (6 > size) // Is there room for 2 borders for each sub box and one character of content each?
1352 // TODO - also should check the contents minimum size.
1353 // No need to check the no border case, that's only for full screen.
1354 // People using terminals smaller than 6 characters get what they deserve.
1355 {
1356 // TODO - put this in the status line, or just silently fail.
1357 fprintf(stderr, "Box is too small to split.\n");
1358 return;
1359 }
1360
1361 // Note that a split box is actually three boxes. The parent, which is not drawn, and the two subs, which are.
1362 // Based on the assumption that there wont be lots of boxes, this keeps things simple.
1363 // It's possible that the box has already been split, and this is called just to update the split.
1364 box->split = split;
1365 if (NULL == box->sub1) // If not split already, do so.
1366 {
1367 box->sub1 = xzalloc(sizeof(struct _box));
1368 box->sub2 = xzalloc(sizeof(struct _box));
1369 cloneBox(box, box->sub1);
1370 cloneBox(box, box->sub2);
1371 if (box->flags & BOX_HSPLIT)
1372 {
1373 // Split the boxes in the middle of the content.
1374 box->sub2->view->offsetY += (box->H * box->split) - 2;
1375 // Figure out which sub box the cursor will be in, then update the cursor in the other box.
1376 if (box->sub1->view->cY < box->sub2->view->offsetY)
1377 box->sub2->view->cY = box->sub2->view->offsetY;
1378 else
1379 {
1380 box->sub1->view->cY = box->sub2->view->offsetY - 1;
1381 otherBox = 1;
1382 }
1383 }
1384 else
1385 {
1386 // Split the boxes in the middle of the content.
1387 box->sub2->view->offsetX += (box->W * box->split) - 2;
1388 // Figure out which sub box the cursor will be in, then update the cursor in the other box.
1389 if (box->sub1->view->cX < box->sub2->view->offsetX)
1390 box->sub2->view->cX = box->sub2->view->offsetX;
1391 else
1392 {
1393 box->sub1->view->cX = box->sub2->view->offsetX - 1;
1394 otherBox = 1;
1395 }
1396 }
1397 }
1398
1399 if ((currentBox == box) && (box->sub1))
1400 {
1401 if (otherBox)
1402 currentBox = box->sub2;
1403 else
1404 currentBox = box->sub1;
1405 }
1406
1407 // Start the recursive recalculation of all the sub boxes.
1408 calcBoxes(box);
1409 drawBoxes(box);
1410}
1411
1412// TODO - Might be better to just have a double linked list of boxes, and traverse that instead.
1413// Except that leaves a problem when deleting boxes, could end up with a blank space.
1414void switchBoxes(view *view)
1415{
1416 box *box = view->box;
1417
1418 // The assumption here is that box == currentBox.
1419 struct _box *oldBox = currentBox;
1420 struct _box *thisBox = box;
1421 int backingUp = 0;
1422
1423 // Depth first traversal.
1424 while ((oldBox == currentBox) && (thisBox->parent))
1425 {
1426 if (thisBox == thisBox->parent->sub1)
1427 {
1428 if (backingUp && (thisBox->parent))
1429 currentBox = thisBox->parent->sub2;
1430 else if (thisBox->sub1)
1431 currentBox = thisBox->sub1;
1432 else
1433 currentBox = thisBox->parent->sub2;
1434 }
1435 else if (thisBox == thisBox->parent->sub2)
1436 {
1437 thisBox = thisBox->parent;
1438 backingUp = 1;
1439 }
1440 }
1441
1442 // If we have not found the next box to move to, move back to the beginning.
1443 if (oldBox == currentBox)
1444 currentBox = rootBox;
1445
1446 // If we ended up on a parent box, go to it's first sub.
1447 while (currentBox->sub1)
1448 currentBox = currentBox->sub1;
1449
1450 // Just redraw them all.
1451 drawBoxes(rootBox);
1452}
1453
1454// TODO - It might be better to do away with this bunch of single line functions
1455// and map script commands directly to lower functions.
1456// How to deal with the various argument needs?
1457// Might be where we can re use the toybox argument stuff.
1458
1459void halveBoxHorizontally(view *view)
1460{
1461 view->box->flags |= BOX_HSPLIT;
1462 splitBox(view->box, 0.5);
1463}
1464
1465void halveBoxVertically(view *view)
1466{
1467 view->box->flags &= ~BOX_HSPLIT;
1468 splitBox(view->box, 0.5);
1469}
1470
1471void switchMode(view *view)
1472{
1473 currentBox->view->mode++;
1474 // Assumes that modes will always have a key mapping, which I think is a safe bet.
1475 if (NULL == currentBox->view->content->context->modes[currentBox->view->mode].keys)
1476 currentBox->view->mode = 0;
1477 commandMode = currentBox->view->content->context->modes[currentBox->view->mode].flags & 1;
1478}
1479
1480void leftChar(view *view)
1481{
1482 moveCursorRelative(view, -1, 0, 0, 0);
1483}
1484
1485void rightChar(view *view)
1486{
1487 moveCursorRelative(view, 1, 0, 0, 0);
1488}
1489
1490void upLine(view *view)
1491{
1492 moveCursorRelative(view, 0, -1, 0, 0);
1493}
1494
1495void downLine(view *view)
1496{
1497 moveCursorRelative(view, 0, 1, 0, 0);
1498}
1499
1500void upPage(view *view)
1501{
1502 moveCursorRelative(view, 0, 0 - (view->H - 1), 0, 0 - (view->H - 1));
1503}
1504
1505void downPage(view *view)
1506{
1507 moveCursorRelative(view, 0, view->H - 1, 0, view->H - 1);
1508}
1509
1510void endOfLine(view *view)
1511{
1512 moveCursorAbsolute(view, strlen(view->prompt) + view->oW, view->cY, 0, 0);
1513}
1514
1515void startOfLine(view *view)
1516{
1517 // TODO - add the advanced editing "smart home".
1518 moveCursorAbsolute(view, strlen(view->prompt), view->cY, 0, 0);
1519}
1520
1521void splitLine(view *view)
1522{
1523 // TODO - should move this into mooshLines().
1524 addLine(view->content, view->line, &(view->line->line[view->iX]), 0);
1525 view->line->line[view->iX] = '\0';
1526 moveCursorAbsolute(view, 0, view->cY + 1, 0, 0);
1527 if (view->box)
1528 drawBox(view->box);
1529}
1530
1531void deleteChar(view *view)
1532{
1533 // TODO - should move this into mooshLines().
1534 // If we are at the end of the line, then join this and the next line.
1535 if (view->oW == view->cX)
1536 {
1537 // Only if there IS a next line.
1538 if (&(view->content->lines) != view->line->next)
1539 {
1540 mooshStrings(view->line, view->line->next->line, view->iX, 1, !overWriteMode);
1541 view->line->next->line = NULL;
1542 freeLine(view->content, view->line->next);
1543 // TODO - should check if we are on the last page, then deal with scrolling.
1544 if (view->box)
1545 drawBox(view->box);
1546 }
1547 }
1548 else
1549 mooshStrings(view->line, NULL, view->iX, 1, !overWriteMode);
1550}
1551
1552void backSpaceChar(view *view)
1553{
1554 if (moveCursorRelative(view, -1, 0, 0, 0))
1555 deleteChar(view);
1556}
1557
1558void saveContent(view *view)
1559{
1560 saveFile(view->content);
1561}
1562
1563void executeLine(view *view)
1564{
1565 struct line *result = view->line;
1566
1567 // Don't bother doing much if there's nothing on this line.
1568 if (result->line[0])
1569 {
1570 doCommand(currentBox->view, result->line);
1571 // If we are not at the end of the history contents.
1572 if (&(view->content->lines) != result->next)
1573 {
1574 struct line *line = view->content->lines.prev;
1575
1576 // Remove the line first.
1577 result->next->prev = result->prev;
1578 result->prev->next = result->next;
1579 // Check if the last line is already blank, then remove it.
1580 if ('\0' == line->line[0])
1581 {
1582 freeLine(view->content, line);
1583 line = view->content->lines.prev;
1584 }
1585 // Then add it to the end.
1586 result->next = line->next;
1587 result->prev = line;
1588 line->next->prev = result;
1589 line->next = result;
1590 view->cY = view->content->lines.length - 1;
1591 }
1592 moveCursorAbsolute(view, 0, view->content->lines.length, 0, 0);
1593 // Make sure there is one blank line at the end.
1594 if ('\0' != view->line->line[0])
1595 {
1596 endOfLine(view);
1597 splitLine(view);
1598 }
1599 }
1600
1601 saveFile(view->content);
1602}
1603
1604void quit(view *view)
1605{
1606 handle_keys_quit();
1607}
1608
1609void nop(view *view)
1610{
1611 // 'tis a nop, don't actually do anything.
1612}
1613
1614
1615typedef void (*CSIhandler) (long extra, int *code, int count);
1616
1617struct CSI
1618{
1619 char *code;
1620 CSIhandler func;
1621};
1622
1623static void termSize(long extra, int *params, int count)
1624{
1625 struct _view *view = (struct _view *) extra; // Though we pretty much stomp on this straight away.
1626 int r = params[0], c = params[1];
1627
1628 // The defaults are 1, which get ignored by the heuristic below.
1629 // Check it's not an F3 key variation, coz some of them use the same CSI function code.
1630 // This is a heuristic, we are checking against an unusable terminal size.
1631 // TODO - Double check what the maximum F3 variations can be.
1632 if ((2 == count) && (8 < r) && (8 < c))
1633 {
1634 commandLine->Y = r;
1635 commandLine->W = c;
1636 rootBox->W = c;
1637 rootBox->H = r - 1;
1638 sizeViewToBox(rootBox, -1, -1, -1, -1);
1639 calcBoxes(rootBox);
1640 drawBoxes(rootBox);
1641
1642 // Move the cursor to where it is, to check it's not now outside the terminal window.
1643 moveCursorAbsolute(rootBox->view, rootBox->view->cX, rootBox->view->cY, 0, 0);
1644
1645 // We have no idea which is the current view now.
1646 if (commandMode) view = commandLine;
1647 else view = currentBox->view;
1648 updateLine(view);
1649 }
1650}
1651
1652struct CSI CSIcommands[] =
1653{
1654 {"R", termSize} // Parameters are cursor line and column. Note this may be sent at other times, not just during terminal resize.
1655};
1656
1657
1658// Callback for incoming sequences from the terminal.
1659static int handleEvent(long extra, struct keyevent *event)
1660{
1661 switch (event->type)
1662 {
1663 case HK_CSI :
1664 {
1665 int j;
1666
1667 for (j = 0; j < ARRAY_LEN(CSIcommands); j++)
1668 {
1669 if (strcmp(CSIcommands[j].code, event->sequence) == 0)
1670 {
1671 CSIcommands[j].func(extra, event->params, event->count);
1672 break;
1673 }
1674 }
1675 break;
1676 }
1677
1678 case HK_KEYS :
1679 {
1680 struct _view *view = (struct _view *) extra; // Though we pretty much stomp on this straight away.
1681 struct keyCommand *commands = currentBox->view->content->context->modes[currentBox->view->mode].keys;
1682 int j, l = strlen(event->sequence);
1683
1684 // Coz things might change out from under us, find the current view.
1685 if (commandMode) view = commandLine;
1686 else view = currentBox->view;
1687
1688 // Search for a key sequence bound to a command.
1689 for (j = 0; commands[j].key; j++)
1690 {
1691 if (strncmp(commands[j].key, event->sequence, l) == 0)
1692 {
1693 // If it's a partial match, keep accumulating them.
1694 if (strlen(commands[j].key) != l)
1695 return 0;
1696 else
1697 {
1698 doCommand(view, commands[j].command);
1699 return 1;
1700 }
1701 }
1702 }
1703
1704 // See if it's ordinary keys.
1705 // NOTE - with vi style ordinary keys can be commands,
1706 // but they would be found by the command check above first.
1707 if (!event->isTranslated)
1708 {
1709 // TODO - Should check for tabs to, and insert them.
1710 // Though better off having a function for that?
1711 mooshStrings(view->line, event->sequence, view->iX, 0, !overWriteMode);
1712 view->oW = formatLine(view, view->line->line, &(view->output));
1713 moveCursorRelative(view, strlen(event->sequence), 0, 0, 0);
1714 updateLine(view);
1715 }
1716 break;
1717 }
1718
1719 default : break;
1720 }
1721
1722 // Tell handle_keys to drop it, coz we dealt with it, or it's not one of ours.
1723 return 1;
1724}
1725
1726
1727// The default command to function mappings, with help text. Any editor that does not have it's own commands can use these for keystroke binding and such.
1728// Though most of the editors have their own variation.
1729// TODO - Maybe just use the joe one as default, it uses short names at least.
1730// Though vi is the only one in POSIX, so might be better to treat that one as the "standard" default.
1731// With some commands from others for stuff vi doesn't support.
1732struct function simpleEditCommands[] =
1733{
1734 {"backSpaceChar", "Back space last character.", 0, {backSpaceChar}},
1735 {"deleteBox", "Delete a box.", 0, {deleteBox}},
1736 {"deleteChar", "Delete current character.", 0, {deleteChar}},
1737 {"downLine", "Move cursor down one line.", 0, {downLine}},
1738 {"downPage", "Move cursor down one page.", 0, {downPage}},
1739 {"endOfLine", "Go to end of line.", 0, {endOfLine}},
1740 {"executeLine", "Execute a line as a script.", 0, {executeLine}},
1741 {"leftChar", "Move cursor left one character.", 0, {leftChar}},
1742 {"quit", "Quit the application.", 0, {quit}},
1743 {"rightChar", "Move cursor right one character.", 0, {rightChar}},
1744 {"save", "Save.", 0, {saveContent}},
1745 {"splitH", "Split box in half horizontally.", 0, {halveBoxHorizontally}},
1746 {"splitLine", "Split line at cursor.", 0, {splitLine}},
1747 {"splitV", "Split box in half vertically.", 0, {halveBoxVertically}},
1748 {"startOfLine", "Go to start of line.", 0, {startOfLine}},
1749 {"switchBoxes", "Switch to another box.", 0, {switchBoxes}},
1750 {"switchMode", "Switch between command and box.", 0, {switchMode}},
1751 {"upLine", "Move cursor up one line.", 0, {upLine}},
1752 {"upPage", "Move cursor up one page.", 0, {upPage}},
1753 {NULL, NULL, 0, {NULL}}
1754};
1755
1756// Construct a simple command line.
1757
1758// The key to command mappings.
1759// TODO - Should not move off the ends of the line to the next / previous line.
1760struct keyCommand simpleCommandKeys[] =
1761{
1762 {"BS", "backSpaceChar"},
1763 {"Del", "deleteChar"},
1764 {"Down", "downLine"},
1765 {"End", "endOfLine"},
1766 {"F10", "quit"},
1767 {"Home", "startOfLine"},
1768 {"Left", "leftChar"},
1769 {"Enter", "executeLine"},
1770 {"Return", "executeLine"},
1771 {"Right", "rightChar"},
1772 {"Esc", "switchMode"},
1773 {"Up", "upLine"},
1774 {NULL, NULL}
1775};
1776
1777
1778// Construct a simple emacs editor.
1779
1780// Mostly control keys, some meta keys.
1781// Ctrl-h and Ctrl-x have more keys in the commands. Some of those extra keys are commands by themselves. Up to "Ctrl-x 4 Ctrl-g" which apparently is identical to just Ctrl-g. shrugs
1782// Ctrl-h is backspace / del. Pffft.
1783// Meta key is either Alt-keystroke, Esc keystroke, or an actual Meta-keystroke (do they still exist?).
1784// TODO - Alt and Meta not supported yet, so using Esc.
1785// Windows commands.
1786
1787// readline uses these same commands, and defaults to emacs keystrokes.
1788struct function simpleEmacsCommands[] =
1789{
1790 {"delete-backward-char", "Back space last character.", 0, {backSpaceChar}},
1791 {"delete-window", "Delete a box.", 0, {deleteBox}},
1792 {"delete-char", "Delete current character.", 0, {deleteChar}},
1793 {"next-line", "Move cursor down one line.", 0, {downLine}},
1794 {"scroll-up", "Move cursor down one page.", 0, {downPage}},
1795 {"end-of-line", "Go to end of line.", 0, {endOfLine}},
1796 {"accept-line", "Execute a line as a script.", 0, {executeLine}}, // From readline, which uses emacs commands, coz mg at least does not seem to have this.
1797 {"backward-char", "Move cursor left one character.", 0, {leftChar}},
1798 {"save-buffers-kill-emacs", "Quit the application.", 0, {quit}}, // TODO - Does more than just quit.
1799 {"forward-char", "Move cursor right one character.", 0, {rightChar}},
1800 {"save-buffer", "Save.", 0, {saveContent}},
1801 {"split-window-horizontally", "Split box in half horizontally.", 0, {halveBoxHorizontally}}, // TODO - Making this one up for now, mg does not have it.
1802 {"newline", "Split line at cursor.", 0, {splitLine}},
1803 {"split-window-vertically", "Split box in half vertically.", 0, {halveBoxVertically}},
1804 {"beginning-of-line", "Go to start of line.", 0, {startOfLine}},
1805 {"other-window", "Switch to another box.", 0, {switchBoxes}}, // There is also "previous-window" for going in the other direction, which we don't support yet.
1806 {"execute-extended-command", "Switch between command and box.", 0, {switchMode}}, // Actually a one time invocation of the command line.
1807 {"previous-line", "Move cursor up one line.", 0, {upLine}},
1808 {"scroll-down", "Move cursor up one page.", 0, {upPage}},
1809 {NULL, NULL, 0, {NULL}}
1810};
1811
1812// The key to command mappings.
1813struct keyCommand simpleEmacsKeys[] =
1814{
1815 {"BS", "delete-backward-char"},
1816 {"Del", "delete-backward-char"},
1817 {"^D", "delete-char"},
1818 {"Down", "next-line"},
1819 {"^N", "next-line"},
1820 {"End", "end-of-line"},
1821 {"^E", "end-of-line"},
1822 {"^X^C", "save-buffers-kill-emacs"},
1823 {"^X^S", "save-buffer"},
1824 {"Home", "beginning-of-line"},
1825 {"^A", "beginning-of-line"},
1826 {"Left", "backward-char"},
1827 {"^B", "backward-char"},
1828 {"PgDn", "scroll-up"},
1829 {"^V", "scroll-up"},
1830 {"PgUp", "scroll-down"},
1831 {"Escv", "scroll-down"}, // M-v
1832 {"Enter", "newline"},
1833 {"Return", "newline"},
1834 {"Right", "forward-char"},
1835 {"^F", "forward-char"},
1836 {"Escx", "execute-extended-command"}, // M-x
1837 {"^X2", "split-window-vertically"},
1838 {"^XP", "other-window"},
1839 {"^X0", "delete-window"},
1840 {"Up", "previous-line"},
1841 {"^P", "previous-line"},
1842 {NULL, NULL}
1843};
1844
1845struct keyCommand simpleEmacsCommandKeys[] =
1846{
1847 {"Del", "delete-backwards-char"},
1848 {"^D", "delete-char"},
1849 {"Down", "next-line"},
1850 {"^N", "next-line"},
1851 {"End", "end-of-line"},
1852 {"^E", "end-of-line"},
1853 {"Home", "beginning-of-line"},
1854 {"^A", "beginning-of-line"},
1855 {"Left", "backward-char"},
1856 {"^B", "backward-char"},
1857 {"Right", "forward-char"},
1858 {"^F", "forward-char"},
1859 {"Up", "previous-line"},
1860 {"^P", "previous-line"},
1861 {"Enter", "accept-line"},
1862 {"Return", "accept-line"},
1863 {"Escx", "execute-extended-command"},
1864 {NULL, NULL}
1865};
1866
1867// An array of various modes.
1868struct mode simpleEmacsMode[] =
1869{
1870 {simpleEmacsKeys, NULL, NULL, 0},
1871 {simpleEmacsCommandKeys, NULL, NULL, 1},
1872 {NULL, NULL, NULL}
1873};
1874
1875// Put it all together into a simple editor context.
1876struct context simpleEmacs =
1877{
1878 simpleEmacsCommands,
1879 simpleEmacsMode,
1880 NULL,
1881 NULL,
1882 NULL
1883};
1884
1885
1886// Construct a simple joe / wordstar editor, using joe is the reference, seems to be the popular Unix variant.
1887// Esc x starts up the command line.
1888// Has multi control key combos. Mostly Ctrl-K, Ctrl-[ (Esc), (Ctrl-B, Ctrl-Q in wordstar and delphi), but might be others.
1889// Can't find a single list of command mappings for joe, gotta search all over. sigh
1890// Even the command line keystroke I stumbled on (Esc x) is not documented.
1891// Note that you don't have to let go of the Ctrl key for the second keystroke, but you can.
1892
1893// From http://joe-editor.sourceforge.net/list.html
1894// TODO - Some of these might be wrong. Just going by the inadequate joe docs for now.
1895struct function simpleJoeCommands[] =
1896{
1897 {"backs", "Back space last character.", 0, {backSpaceChar}},
1898 {"abort", "Delete a box.", 0, {deleteBox}}, // TODO - Should do quit if it's the last window.
1899 {"delch", "Delete current character.", 0, {deleteChar}},
1900 {"dnarw", "Move cursor down one line.", 0, {downLine}},
1901 {"pgdn", "Move cursor down one page.", 0, {downPage}},
1902 {"eol", "Go to end of line.", 0, {endOfLine}},
1903 {"ltarw", "Move cursor left one character.", 0, {leftChar}},
1904 {"killjoe", "Quit the application.", 0, {quit}},
1905 {"rtarw", "Move cursor right one character.", 0, {rightChar}},
1906 {"save", "Save.", 0, {saveContent}},
1907 {"splitw", "Split box in half horizontally.", 0, {halveBoxHorizontally}},
1908 {"open", "Split line at cursor.", 0, {splitLine}},
1909 {"bol", "Go to start of line.", 0, {startOfLine}},
1910 {"home", "Go to start of line.", 0, {startOfLine}},
1911 {"nextw", "Switch to another box.", 0, {switchBoxes}}, // This is "next window", there's also "previous window" which we don't support yet.
1912 {"execmd", "Switch between command and box.", 0, {switchMode}}, // Actually I think this just switches to the command mode, not back and forth. Or it might execute the actual command.
1913 {"uparw", "Move cursor up one line.", 0, {upLine}},
1914 {"pgup", "Move cursor up one page.", 0, {upPage}},
1915
1916 // Not an actual joe command.
1917 {"executeLine", "Execute a line as a script.", 0, {executeLine}}, // Perhaps this should be execmd?
1918 {NULL, NULL, 0, {NULL}}
1919};
1920
1921struct keyCommand simpleJoeKeys[] =
1922{
1923 {"BS", "backs"},
1924 {"^D", "delch"},
1925 {"Down", "dnarw"},
1926 {"^N", "dnarw"},
1927 {"^E", "eol"},
1928 {"^C", "killjoe"},
1929 {"^Kd", "save"},
1930 {"^K^D" "save"},
1931 {"^A", "bol"},
1932 {"Left", "ltarw"},
1933 {"^B", "ltarw"},
1934 {"^V", "pgdn"}, // Actually half a page.
1935 {"^U", "pgup"}, // Actually half a page.
1936 {"Enter", "open"},
1937 {"Return", "open"},
1938 {"Right", "rtarw"},
1939 {"^F", "rtarw"},
1940 {"Escx", "execmd"},
1941 {"Esc^X", "execmd"},
1942 {"^Ko", "splitw"},
1943 {"^K^O", "splitw"},
1944 {"^Kn", "nextw"},
1945 {"^K^N", "nextw"},
1946 {"^Kx", "killjoe"}, // TODO - Should ask if it should save if it's been modified. A good generic thing to do anyway.
1947 {"^K^X", "abort"}, // TODO - These two both close a window, and quit if that was the last window.
1948 {"Up", "uparw"},
1949 {"^P", "uparw"},
1950 {NULL, NULL}
1951};
1952
1953struct keyCommand simpleJoeCommandKeys[] =
1954{
1955 {"BS", "backs"},
1956 {"^D", "delch"},
1957 {"Down", "dnarw"},
1958 {"^N", "dnarw"},
1959 {"^E", "eol"},
1960 {"^A", "bol"},
1961 {"Left", "ltarw"},
1962 {"^B", "ltarw"},
1963 {"Right", "rtarw"},
1964 {"^F", "rtarw"},
1965 {"Escx", "execmd"},
1966 {"Esc^X", "execmd"},
1967 {"Up", "uparw"},
1968 {"^P", "uparw"},
1969 {"Enter", "executeLine"},
1970 {"Return", "executeLine"},
1971 {NULL, NULL}
1972};
1973
1974struct mode simpleJoeMode[] =
1975{
1976 {simpleJoeKeys, NULL, NULL, 0},
1977 {simpleJoeCommandKeys, NULL, NULL, 1},
1978 {NULL, NULL, NULL, 0}
1979};
1980
1981struct context simpleJoe =
1982{
1983 simpleJoeCommands,
1984 simpleJoeMode,
1985 NULL,
1986 NULL,
1987 NULL
1988};
1989
1990
1991// Simple more and / or less.
1992// '/' and '?' for search command mode. I think they both have some ex commands with the usual : command mode starter.
1993// No cursor movement, just scrolling.
1994// TODO - Put content into read only mode.
1995// TODO - actually implement read only mode where up and down one line do actual scrolling instead of cursor movement.
1996
1997struct keyCommand simpleLessKeys[] =
1998{
1999 {"Down", "downLine"},
2000 {"j", "downLine"},
2001 {"Enter", "downLine"},
2002 {"Return", "downLine"},
2003 {"End", "endOfLine"},
2004 {"q", "quit"},
2005 {":q", "quit"}, // TODO - A vi ism, should do ex command stuff instead.
2006 {"ZZ", "quit"},
2007 {"PgDn", "downPage"},
2008 {"f", "downPage"},
2009 {" ", "downPage"},
2010 {"^F", "downPage"},
2011 {"Left", "leftChar"},
2012 {"Right", "rightChar"},
2013 {"PgUp", "upPage"},
2014 {"b", "upPage"},
2015 {"^B", "upPage"},
2016 {"Up", "upLine"},
2017 {"k", "upLine"},
2018 {NULL, NULL}
2019};
2020
2021struct mode simpleLessMode[] =
2022{
2023 {simpleLessKeys, NULL, NULL, 0},
2024 {simpleCommandKeys, NULL, NULL, 1},
2025 {NULL, NULL, NULL}
2026};
2027
2028struct context simpleLess =
2029{
2030 simpleEditCommands,
2031 simpleLessMode,
2032 NULL,
2033 NULL,
2034 NULL
2035};
2036
2037struct keyCommand simpleMoreKeys[] =
2038{
2039 {"j", "downLine"},
2040 {"Enter", "downLine"},
2041 {"Return", "downLine"},
2042 {"q", "quit"},
2043 {":q", "quit"}, // See comments for "less".
2044 {"ZZ", "quit"},
2045 {"f", "downPage"},
2046 {" ", "downPage"},
2047 {"^F", "downPage"},
2048 {"b", "upPage"},
2049 {"^B", "upPage"},
2050 {"k", "upLine"},
2051 {NULL, NULL}
2052};
2053
2054struct mode simpleMoreMode[] =
2055{
2056 {simpleMoreKeys, NULL, NULL, 0},
2057 {simpleCommandKeys, NULL, NULL, 1},
2058 {NULL, NULL, NULL}
2059};
2060
2061struct context simpleMore =
2062{
2063 simpleEditCommands,
2064 simpleMoreMode,
2065 NULL,
2066 NULL,
2067 NULL
2068};
2069
2070
2071// Construct a simple mcedit / cool edit editor.
2072
2073struct keyCommand simpleMceditKeys[] =
2074{
2075 {"BS", "backSpaceChar"},
2076 {"Del", "deleteChar"},
2077 {"Down", "downLine"},
2078 {"End", "endOfLine"},
2079 {"F10", "quit"},
2080 {"Esc0", "quit"},
2081 {"F2", "save"},
2082 {"Esc2", "save"},
2083 {"Home", "startOfLine"},
2084 {"Left", "leftChar"},
2085 {"PgDn", "downPage"},
2086 {"PgUp", "upPage"},
2087 {"Enter", "splitLine"},
2088 {"Return", "splitLine"},
2089 {"Right", "rightChar"},
2090 {"Shift F2", "switchMode"}, // MC doesn't have a command mode.
2091 {"Esc:", "switchMode"}, // Sorta vi like, and coz tmux is screwing with the shift function keys somehow.
2092 {"Esc|", "splitV"}, // MC doesn't have a split window concept, so make these up to match tmux more or less.
2093 {"Esc-", "splitH"},
2094 {"Esco", "switchBoxes"},
2095 {"Escx", "deleteBox"},
2096 {"Up", "upLine"},
2097{"^C", "switchMode"}, // To test the signal stopper.
2098{"^D", "switchMode"}, // To test the signal stopper.
2099{"^Q", "switchMode"}, // To test the signal stopper.
2100{"^S", "switchMode"}, // To test the signal stopper.
2101{"^T", "switchMode"}, // To test the signal stopper.
2102{"^Z", "switchMode"}, // To test the signal stopper.
2103{"^\\", "switchMode"}, // To test the signal stopper.
2104{"F1", "switchMode"}, // To test various function keys.
2105{"F3", "switchMode"}, // To test various function keys.
2106{"F4", "switchMode"}, // To test various function keys.
2107{"F5", "switchMode"}, // To test various function keys.
2108{"F6", "switchMode"}, // To test various function keys.
2109{"F7", "switchMode"}, // To test various function keys.
2110{"F8", "switchMode"}, // To test various function keys.
2111{"F9", "switchMode"}, // To test various function keys.
2112 {NULL, NULL}
2113};
2114
2115struct mode simpleMceditMode[] =
2116{
2117 {simpleMceditKeys, NULL, NULL, 0},
2118 {simpleCommandKeys, NULL, NULL, 1},
2119 {NULL, NULL, NULL}
2120};
2121
2122struct context simpleMcedit =
2123{
2124 simpleEditCommands,
2125 simpleMceditMode,
2126 NULL,
2127 NULL,
2128 NULL
2129};
2130
2131
2132// Simple nano editor.
2133// Has key to function bindings, but no command line mode. Has "enter parameter on this line" mode for some commands.
2134// Control and meta keys, only singles, unlike emacs and joe.
2135// Can have multiple buffers, but no windows. Think I can skip that for simple editor.
2136
2137struct function simpleNanoCommands[] =
2138{
2139 {"backSpaceChar", "Back space last character.", 0, {backSpaceChar}},
2140 {"delete", "Delete current character.", 0, {deleteChar}},
2141 {"down", "Move cursor down one line.", 0, {downLine}},
2142 {"downPage", "Move cursor down one page.", 0, {downPage}},
2143 {"end", "Go to end of line.", 0, {endOfLine}},
2144 {"left", "Move cursor left one character.", 0, {leftChar}},
2145 {"exit", "Quit the application.", 0, {quit}},
2146 {"right", "Move cursor right one character.", 0, {rightChar}},
2147 {"writeout", "Save.", 0, {saveContent}},
2148 {"enter", "Split line at cursor.", 0, {splitLine}},
2149 {"home", "Go to start of line.", 0, {startOfLine}},
2150 {"up", "Move cursor up one line.", 0, {upLine}},
2151 {"upPage", "Move cursor up one page.", 0, {upPage}},
2152 {NULL, NULL, 0, {NULL}}
2153};
2154
2155
2156// Hmm, back space, page up, and page down don't seem to have bindable commands according to the web page, but they are bound to keys anyway.
2157struct keyCommand simpleNanoKeys[] =
2158{
2159// TODO - Delete key is ^H dammit. Find the alternate Esc sequence for Del.
2160// {"^H", "backSpaceChar"}, // ?
2161 {"BS", "backSpaceChar"},
2162 {"^D", "delete"},
2163 {"Del", "delete"},
2164 {"^N", "down"},
2165 {"Down", "down"},
2166 {"^E", "end"},
2167 {"End", "end"},
2168 {"^X", "exit"},
2169 {"F2", "exit"},
2170 {"^O", "writeout"},
2171 {"F3", "writeout"},
2172 {"^A", "home"},
2173 {"Home", "home"},
2174 {"^B", "left"},
2175 {"Left", "left"},
2176 {"^V", "downPage"}, // ?
2177 {"PgDn", "downPage"},
2178 {"^Y", "upPage"}, // ?
2179 {"PgUp", "upPage"},
2180 {"Enter", "enter"}, // TODO - Not sure if this is correct.
2181 {"Return", "enter"}, // TODO - Not sure if this is correct.
2182 {"^F", "right"},
2183 {"Right", "right"},
2184 {"^P", "up"},
2185 {"Up", "up"},
2186 {NULL, NULL}
2187};
2188
2189struct mode simpleNanoMode[] =
2190{
2191 {simpleNanoKeys, NULL, NULL, 0},
2192 {NULL, NULL, NULL}
2193};
2194
2195struct context simpleNano =
2196{
2197 simpleNanoCommands,
2198 simpleNanoMode,
2199 NULL,
2200 NULL,
2201 NULL
2202};
2203
2204
2205// Construct a simple vi editor.
2206// Only vi is not so simple. lol
2207// The "command line" modes are /, ?, :, and !,
2208// / is regex search.
2209// ? is regex search backwards.
2210// : is ex command mode.
2211// ! is replace text with output from shell command mode.
2212// Arrow keys do the right thing in "normal" mode, but not in insert mode.
2213// "i" goes into insert mode, "Esc" (or Ctrl-[ or Ctrl-C) gets you out. So much for "you can do it all touch typing on the home row". Pffft
2214// Ah, the Esc key WAS a lot closer to the home row (where Tab usually is now) on the original keyboard vi was designed for, ADM3A.
2215// Which is also the keyboard with the arrow keys marked on h, j, k, and l keys.
2216// Did I mention that vi is just a horrid historic relic that should have died long ago?
2217// Emacs looks to have the same problem, originally designed for an ancient keyboard that is nothing like what people actually use these days.
2218// "h", "j", "k", "l" move cursor, which is just random keys for dvorak users.
2219// ":" goes into ex command mode.
2220// ":q" deletes current window in vim.
2221// ":qa!" goes into ex mode and does some sort of quit command.
2222// The 'q' is short for quit, the ! is an optional argument to quit. No idea yet what the a is for, all windows?
2223// Del or "x" to delete a character. Del in insert mode.
2224// "X" to backspace. BS or Ctrl-H to backspace in insert mode.
2225// NOTE - Backspace in normal mode just moves left.
2226// Tab or Ctrl-I to insert a tab in insert mode.
2227// Return in normal mode goes to the start of the next line, or splits the line in insert mode.
2228// ":help" opens a window with help text.
2229// Vim window commands.
2230
2231// Vi needs extra variables and functions.
2232static int viTempExMode;
2233
2234void viMode(view *view)
2235{
2236 currentBox->view->mode = 0;
2237 commandMode = 0;
2238 viTempExMode = 0;
2239}
2240
2241void viInsertMode(view *view)
2242{
2243 currentBox->view->mode = 1;
2244 commandMode = 0;
2245}
2246
2247void viExMode(view *view)
2248{
2249 currentBox->view->mode = 2;
2250 commandMode = 1;
2251 // TODO - Should change this based on the event, : or Q.
2252 viTempExMode = 1;
2253 commandLine->prompt = xrealloc(commandLine->prompt, 2);
2254 strcpy(commandLine->prompt, ":");
2255}
2256
2257void viBackSpaceChar(view *view)
2258{
2259 if ((2 == currentBox->view->mode) && (0 == view->cX) && viTempExMode)
2260 viMode(view);
2261 else
2262 backSpaceChar(view);
2263}
2264
2265void viStartOfNextLine(view *view)
2266{
2267 startOfLine(view);
2268 downLine(view);
2269}
2270
2271struct function simpleViCommands[] =
2272{
2273 // These are actual ex commands.
2274 {"insert", "Switch to insert mode.", 0, {viInsertMode}},
2275 {"quit", "Quit the application.", 0, {quit}},
2276 {"visual", "Switch to visual mode.", 0, {viMode}},
2277 {"write", "Save.", 0, {saveContent}},
2278
2279 // These are not ex commands.
2280 {"backSpaceChar", "Back space last character.", 0, {viBackSpaceChar}},
2281 {"deleteBox", "Delete a box.", 0, {deleteBox}},
2282 {"deleteChar", "Delete current character.", 0, {deleteChar}},
2283 {"downLine", "Move cursor down one line.", 0, {downLine}},
2284 {"downPage", "Move cursor down one page.", 0, {downPage}},
2285 {"endOfLine", "Go to end of line.", 0, {endOfLine}},
2286 {"executeLine", "Execute a line as a script.", 0, {executeLine}},
2287 {"exMode", "Switch to ex mode.", 0, {viExMode}},
2288 {"leftChar", "Move cursor left one character.", 0, {leftChar}},
2289 {"rightChar", "Move cursor right one character.", 0, {rightChar}},
2290 {"splitH", "Split box in half horizontally.", 0, {halveBoxHorizontally}},
2291 {"splitLine", "Split line at cursor.", 0, {splitLine}},
2292 {"splitV", "Split box in half vertically.", 0, {halveBoxVertically}},
2293 {"startOfLine", "Go to start of line.", 0, {startOfLine}},
2294 {"startOfNLine", "Go to start of next line.", 0, {viStartOfNextLine}},
2295 {"switchBoxes", "Switch to another box.", 0, {switchBoxes}},
2296 {"upLine", "Move cursor up one line.", 0, {upLine}},
2297 {"upPage", "Move cursor up one page.", 0, {upPage}},
2298 {NULL, NULL, 0, {NULL}}
2299};
2300
2301struct keyCommand simpleViNormalKeys[] =
2302{
2303 {"BS", "leftChar"},
2304 {"X", "backSpaceChar"},
2305 {"Del", "deleteChar"},
2306 {"x", "deleteChar"},
2307 {"Down", "downLine"},
2308 {"j", "downLine"},
2309 {"End", "endOfLine"},
2310 {"Home", "startOfLine"},
2311 {"Left", "leftChar"},
2312 {"h", "leftChar"},
2313 {"PgDn", "downPage"},
2314 {"^F", "downPage"},
2315 {"PgUp", "upPage"},
2316 {"^B", "upPage"},
2317 {"Enter", "startOfNextLine"},
2318 {"Return", "startOfNextLine"},
2319 {"Right", "rightChar"},
2320 {"l", "rightChar"},
2321 {"i", "insert"},
2322 {":", "exMode"}, // This is the temporary ex mode that you can backspace out of. Or any command backs you out.
2323 {"Q", "exMode"}, // This is the ex mode you need to do the "visual" command to get out of.
2324 {"^Wv", "splitV"},
2325 {"^W^V", "splitV"},
2326 {"^Ws", "splitH"},
2327 {"^WS", "splitH"},
2328 {"^W^S", "splitH"},
2329 {"^Ww", "switchBoxes"},
2330 {"^W^W", "switchBoxes"},
2331 {"^Wq", "deleteBox"},
2332 {"^W^Q", "deleteBox"},
2333 {"Up", "upLine"},
2334 {"k", "upLine"},
2335 {NULL, NULL}
2336};
2337
2338struct keyCommand simpleViInsertKeys[] =
2339{
2340 {"BS", "backSpaceChar"},
2341 {"Del", "deleteChar"},
2342 {"Return", "splitLine"},
2343 {"Esc", "visual"},
2344 {"^C", "visual"},
2345 {NULL, NULL}
2346};
2347
2348struct keyCommand simpleExKeys[] =
2349{
2350 {"BS", "backSpaceChar"},
2351 {"Del", "deleteChar"},
2352 {"Down", "downLine"},
2353 {"End", "endOfLine"},
2354 {"Home", "startOfLine"},
2355 {"Left", "leftChar"},
2356 {"Enter", "executeLine"},
2357 {"Return", "executeLine"},
2358 {"Right", "rightChar"},
2359 {"Esc", "visual"},
2360 {"Up", "upLine"},
2361 {NULL, NULL}
2362};
2363
2364struct mode simpleViMode[] =
2365{
2366 {simpleViNormalKeys, NULL, NULL, 0},
2367 {simpleViInsertKeys, NULL, NULL, 0},
2368 {simpleExKeys, NULL, NULL, 1},
2369 {NULL, NULL, NULL}
2370};
2371
2372struct context simpleVi =
2373{
2374 simpleViCommands,
2375 simpleViMode,
2376 NULL,
2377 NULL,
2378 NULL
2379};
2380
2381
2382// TODO - simple sed editor? May be out of scope for "simple", so leave it until later?
2383// Probably entirely useless for "simple".
2384
2385
2386// TODO - have any unrecognised escape key sequence start up a new box (split one) to show the "show keys" content.
2387// That just adds each "Key is X" to the end of the content, and allows scrolling, as well as switching between other boxes.
2388
2389void boxes_main(void)
2390{
2391 struct context *context = &simpleMcedit; // The default is mcedit, coz that's what I use.
2392 struct termios termio, oldtermio;
2393 char *prompt = "Enter a command : ";
2394 unsigned W = 80, H = 24;
2395 lua_State *L;
2396
2397 /*
2398 * All Lua contexts are held in this structure. We work with it almost
2399 * all the time.
2400 */
2401 L = luaL_newstate();
2402
2403/* From http://luajit.org/install.html -
2404To change or extend the list of standard libraries to load, copy
2405src/lib_init.c to your project and modify it accordingly. Make sure the
2406jit library is loaded or the JIT compiler will not be activated.
2407*/
2408 luaL_openlibs(L); /* Load Lua libraries */
2409
2410 // For testing purposes, figure out which context we use. When this gets real, the toybox multiplexer will sort this out for us instead.
2411 if (toys.optflags & FLAG_m)
2412 {
2413 if (strcmp(TT.mode, "emacs") == 0)
2414 context = &simpleEmacs;
2415 else if (strcmp(TT.mode, "joe") == 0)
2416 context = &simpleJoe;
2417 else if (strcmp(TT.mode, "less") == 0)
2418 context = &simpleLess;
2419 else if (strcmp(TT.mode, "mcedit") == 0)
2420 context = &simpleMcedit;
2421 else if (strcmp(TT.mode, "more") == 0)
2422 context = &simpleMore;
2423 else if (strcmp(TT.mode, "nano") == 0)
2424 context = &simpleNano;
2425 else if (strcmp(TT.mode, "vi") == 0)
2426 context = &simpleVi;
2427 }
2428
2429 // TODO - Should do an isatty() here, though not sure about the usefullness of driving this from a script or redirected input, since it's supposed to be a UI for terminals.
2430 // It would STILL need the terminal size for output though. Perhaps just bitch and abort if it's not a tty?
2431 // On the other hand, sed don't need no stinkin' UI. And things like more or less should be usable on the end of a pipe.
2432
2433 // Grab the old terminal settings and save it.
2434 tcgetattr(0, &oldtermio);
2435 tcflush(0, TCIFLUSH);
2436 termio = oldtermio;
2437
2438 // Mould the terminal to our will.
2439 /*
2440 IUCLC (not in POSIX) Map uppercase characters to lowercase on input.
2441 IXON Enable XON/XOFF flow control on output.
2442 IXOFF Enable XON/XOFF flow control on input.
2443 IXANY (not in POSIX.1; XSI) Enable any character to restart output.
2444
2445 ECHO Echo input characters.
2446 ECHOE If ICANON is also set, the ERASE character erases the preceding input character, and WERASE erases the preceding word.
2447 ECHOK If ICANON is also set, the KILL character erases the current line.
2448 ECHONL If ICANON is also set, echo the NL character even if ECHO is not set.
2449 TOSTOP Send the SIGTTOU signal to the process group of a background process which tries to write to its controlling terminal.
2450 ICANON Enable canonical mode. This enables the special characters EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, and WERASE, and buffers by lines.
2451
2452 VTIME Timeout in deciseconds for non-canonical read.
2453 VMIN Minimum number of characters for non-canonical read.
2454
2455 raw mode turning off ICANON, IEXTEN, and ISIG kills most special key processing.
2456 termio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
2457 termio.c_oflag &= ~OPOST;
2458 termio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
2459 termio.c_cflag &= ~(CSIZE | PARENB);
2460 termio.c_cflag |= CS8;
2461
2462 IGNBRK ignore BREAK
2463 BRKINT complicated, bet in this context, sends BREAK as '\x00'
2464 PARMRK characters with parity or frame erors are sent as '\x00'
2465 ISTRIP strip 8th byte
2466 INLCR translate LF to CR in input
2467 IGLCR ignore CR
2468 OPOST enable implementation defined output processing
2469 ISIG generate signals on INTR (SIGINT on ^C), QUIT (SIGQUIT on ^\\), SUSP (SIGTSTP on ^Z), DSUSP (SIGTSTP on ^Y)
2470 IEXTEN enable implementation defined input processing, turns on some key -> signal -tuff
2471 CSIZE mask for character sizes, so in this case, we mask them all out
2472 PARENB enable parity
2473 CS8 8 bit characters
2474
2475 VEOF "sends" EOF on ^D, ICANON turns that on.
2476 VSTART restart output on ^Q, IXON turns that on.
2477 VSTATUS display status info and sends SIGINFO (STATUS) on ^T. Not in POSIX, not supported in Linux. ICANON turns that on.
2478 VSTOP stop output on ^S, IXON turns that on.
2479 */
2480 termio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IUCLC | IXON | IXOFF | IXANY);
2481 termio.c_oflag &= ~OPOST;
2482 termio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | TOSTOP | ICANON | ISIG | IEXTEN);
2483 termio.c_cflag &= ~(CSIZE | PARENB);
2484 termio.c_cflag |= CS8;
2485 termio.c_cc[VTIME]=0; // deciseconds.
2486 termio.c_cc[VMIN]=1;
2487 tcsetattr(0, TCSANOW, &termio);
2488
2489 terminal_size(&W, &H);
2490 if (toys.optflags & FLAG_w)
2491 W = TT.w;
2492 if (toys.optflags & FLAG_h)
2493 H = TT.h;
2494
2495 // Create the main box. Right now the system needs one for wrapping around while switching. The H - 1 bit is to leave room for our example command line.
2496 rootBox = addBox("root", context, toys.optargs[0], 0, 0, W, H - 1);
2497 currentBox = rootBox;
2498
2499 // Create the command line view, sharing the same context as the root. It will differentiate based on the view mode of the current box.
2500 // Also load the command line history as it's file.
2501 // TODO - different contexts will have different history files, though what to do about ones with no history, and ones with different histories for different modes?
2502 commandLine = addView("command", rootBox->view->content->context, ".boxes.history", 0, H, W, 1);
2503 // Add a prompt to it.
2504 commandLine->prompt = xrealloc(commandLine->prompt, strlen(prompt) + 1);
2505 strcpy(commandLine->prompt, prompt);
2506 // Move to the end of the history.
2507 moveCursorAbsolute(commandLine, 0, commandLine->content->lines.length, 0, 0);
2508
2509 // All the mouse tracking methods suck one way or another. sigh
2510 // http://rtfm.etla.org/xterm/ctlseq.html documents xterm stuff, near the bottom is the mouse stuff.
2511 // http://leonerds-code.blogspot.co.uk/2012/04/wide-mouse-support-in-libvterm.html is helpful.
2512 // Enable mouse (VT200 normal tracking mode, UTF8 encoding). The limit is 2015. Seems to only be in later xterms.
2513// fputs("\x1B[?1005h", stdout);
2514 // Enable mouse (VT340 locator reporting mode). In theory has no limit. Wont actually work though.
2515 // On the other hand, only allows for four buttons, so only half a mouse wheel.
2516 // Responds with "\1B[e;p;r;c;p&w" where e is event type, p is a bitmap of buttons pressed, r and c are the mouse coords in decimal, and p is the "page number".
2517// fputs("\x1B[1;2'z\x1B[1;3'{", stdout);
2518 // Enable mouse (VT200 normal tracking mode). Has a limit of 256 - 32 rows and columns. An xterm exclusive I think, but works in roxterm at least. No wheel reports.
2519 // Responds with "\x1B[Mbxy" where x and y are the mouse coords, and b is bit encoded buttons and modifiers - 0=MB1 pressed, 1=MB2 pressed, 2=MB3 pressed, 3=release, 4=Shift, 8=Meta, 16=Control
2520// fputs("\x1B[?1000h", stdout);
2521// fflush(stdout);
2522
2523 calcBoxes(currentBox);
2524 drawBoxes(currentBox);
2525 // Do the first cursor update.
2526 updateLine(currentBox->view);
2527
2528 // Run the main loop.
2529 handle_keys((long) currentBox->view, handleEvent);
2530
2531 // TODO - Should remember to turn off mouse reporting when we leave.
2532
2533 // Restore the old terminal settings.
2534 tcsetattr(0, TCSANOW, &oldtermio);
2535
2536
2537 if (toys.optflags & FLAG_m)
2538 {
2539 if (strcmp(TT.mode, "sh") == 0)
2540 {
2541 int status, result, i;
2542 double sum;
2543
2544 /* Load the file containing the script we are going to run */
2545 status = luaL_loadfile(L, "toys/boxes/script.lua");
2546 if (status)
2547 {
2548 /* If something went wrong, error message is at the top of */
2549 /* the stack */
2550 fprintf(stderr, "Couldn't load file: %s\n", lua_tostring(L, -1));
2551 exit(1);
2552 }
2553
2554 /*
2555 * Ok, now here we go: We pass data to the lua script on the stack.
2556 * That is, we first have to prepare Lua's virtual stack the way we
2557 * want the script to receive it, then ask Lua to run it.
2558 */
2559 lua_newtable(L); /* We will pass a table */
2560
2561 /*
2562 * To put values into the table, we first push the index, then the
2563 * value, and then call lua_rawset() with the index of the table in the
2564 * stack. Let's see why it's -3: In Lua, the value -1 always refers to
2565 * the top of the stack. When you create the table with lua_newtable(),
2566 * the table gets pushed into the top of the stack. When you push the
2567 * index and then the cell value, the stack looks like:
2568 *
2569 * <- [stack bottom] -- table, index, value [top]
2570 *
2571 * So the -1 will refer to the cell value, thus -3 is used to refer to
2572 * the table itself. Note that lua_rawset() pops the two last elements
2573 * of the stack, so that after it has been called, the table is at the
2574 * top of the stack.
2575 */
2576 for (i = 1; i <= 5; i++)
2577 {
2578 lua_pushnumber(L, i); /* Push the table index */
2579 lua_pushnumber(L, i*2); /* Push the cell value */
2580 lua_rawset(L, -3); /* Stores the pair in the table */
2581 }
2582
2583 /* By what name is the script going to reference our table? */
2584 lua_setglobal(L, "foo");
2585
2586 /* Ask Lua to run our little script */
2587 result = lua_pcall(L, 0, LUA_MULTRET, 0);
2588 if (result)
2589 {
2590 fprintf(stderr, "Failed to run script: %s\n", lua_tostring(L, -1));
2591 exit(1);
2592 }
2593
2594 /* Get the returned value at the top of the stack (index -1) */
2595 sum = lua_tonumber(L, -1);
2596
2597 printf("Script returned: %.0f\n", sum);
2598
2599 lua_pop(L, 1); /* Take the returned value out of the stack */
2600 }
2601 }
2602
2603 lua_close(L); /* Cya, Lua */
2604
2605 puts("");
2606 fflush(stdout);
2607}
diff --git a/src/build.sh b/src/build.sh
new file mode 100755
index 0000000..6e027b7
--- /dev/null
+++ b/src/build.sh
@@ -0,0 +1,67 @@
1#!/bin/bash
2
3# Poor mans git sub modules, coz otherwise it gets complex.
4if [ ! -d git-sub-modules/fcgi2 ]; then
5 pushd git-sub-modules
6 git clone https://github.com/FastCGI-Archives/fcgi2.git
7 popd
8 ln -fs git-sub-modules/fcgi2 fcgi2
9else
10 pushd git-sub-modules/fcgi2
11 git pull
12 popd
13fi
14
15pushd fcgi2 >/dev/null
16make distclean
17./autogen.sh
18./configure
19sed -e "s/#define PACKAGE/#define FCGI_PACKAGE/g" -i fcgi_config.h
20sed -e "s/#define VERSION /#define FCGI_VERSION /g" -i fcgi_config.h
21make
22popd >/dev/null
23
24
25if [ ! -d git-sub-modules/luajit-2.0 ]; then
26 pushd git-sub-modules
27 git clone https://luajit.org/git/luajit-2.0.git
28 popd
29 ln -fs git-sub-modules/luajit-2.0 luajit
30else
31 pushd git-sub-modules/luajit-2.0
32 git pull
33 popd
34fi
35pushd luajit >/dev/null
36make clean
37make amalg
38popd >/dev/null
39
40
41if [ ! -d git-sub-modules/qlibc ]; then
42 pushd git-sub-modules
43 git clone https://github.com/wolkykim/qlibc.git
44 popd
45 ln -fs git-sub-modules/qlibc qlibc
46else
47 pushd git-sub-modules/qlibc
48 git pull
49 popd
50fi
51pushd qlibc >/dev/null
52make clean
53./configure
54make
55popd >/dev/null
56
57
58export CFLAGS="-Iluajit/src -Ifcgi2 -Ifcgi2/include -Iqlibc/include/qlibc $(mysql_config --cflags)"
59export LDFLAGS="-Lluajit/src -Lfcgi2/libfcgi/.libs -Lqlibc/lib $(mysql_config --libs) -Wl,-E -l:libluajit.a -l:libfcgi.a -l:libqlibcext.a -l:libqlibc.a -lm -ldl"
60gcc $CFLAGS sledjchisl.c -o sledjchisl toybox.c $LDFLAGS || exit 1
61
62
63sudo killall -TERM sledjchisl.fcgi
64sleep 2
65sudo killall -KILL sledjchisl.fcgi
66sudo ln -fs $(pwd)/sledjchisl /var/www/fcgi-bin/sledjchisl.fcgi
67sudo ln -fs $(pwd)/.sledjChisl.conf.lua /var/www/fcgi-bin/
diff --git a/src/dumbsh.c b/src/dumbsh.c
new file mode 100644
index 0000000..9ad0204
--- /dev/null
+++ b/src/dumbsh.c
@@ -0,0 +1,283 @@
1/* dumbsh.c - A really dumb shell, to demonstrate handle_keys usage.
2 *
3 * Copyright 2014 David Seikel <won_fang@yahoo.com.au>
4 *
5 * Not a real shell, so doesn't follow any standards,
6 * coz it wont implement them anyway.
7
8USE_DUMBSH(NEWTOY(dumbsh, "", TOYFLAG_USR|TOYFLAG_BIN))
9
10config DUMBSH
11 bool "dumbsh"
12 default n
13 help
14 usage: dumbsh
15
16 A really dumb shell.
17*/
18
19#include "toys.h"
20#include "lib/handlekeys.h"
21
22typedef void (*eventHandler) (void);
23
24struct keyCommand
25{
26 char *key;
27 eventHandler handler;
28};
29
30GLOBALS(
31 unsigned h, w;
32 int x, y;
33 struct double_list *history;
34)
35
36#define TT this.dumbsh
37
38// Sanity check cursor location and update the current line.
39static void updateLine()
40{
41 if (0 > TT.x) TT.x = 0;
42 if (0 > TT.y) TT.y = 0;
43 if (TT.w < TT.x) TT.x = TT.w;
44 if (TT.h < TT.y) TT.y = TT.h;
45 if (strlen(toybuf) < TT.x) TT.x = strlen(toybuf);
46 printf("\x1B[%d;0H%-*s\x1B[%d;%dH",
47 TT.y + 1, TT.w, toybuf, TT.y + 1, TT.x + 1);
48 fflush(stdout);
49}
50
51// The various commands.
52static void deleteChar()
53{
54 int j;
55
56 for (j = TT.x; toybuf[j]; j++)
57 toybuf[j] = toybuf[j + 1];
58 updateLine();
59}
60
61static void backSpaceChar()
62{
63 if (TT.x)
64 {
65 TT.x--;
66 deleteChar();
67 }
68}
69
70// This is where we would actually deal with
71// what ever command the user had typed in.
72// For now we just move on to the next line.
73// TODO - We would want to redirect I/O, capture some keys (^C),
74// but pass the rest on.
75// Dunno yet how to deal with that.
76// We still want handle_keys to be doing it's thing,
77// so maybe handing it another fd, and a callback.
78// A function to add and remove fd and callback pairs for
79// handle_keys to check?
80static void doCommand()
81{
82 toybuf[0] = 0;
83 TT.x = 0;
84 TT.y++;
85 printf("\n");
86 fflush(stdout);
87 updateLine();
88}
89
90static void endOfLine()
91{
92 TT.x = strlen(toybuf);
93 updateLine();
94}
95
96static void leftChar()
97{
98 TT.x--;
99 updateLine();
100}
101
102static void nextHistory()
103{
104 TT.history = TT.history->next;
105 strcpy(toybuf, TT.history->data);
106 TT.x = strlen(toybuf);
107 updateLine();
108}
109
110static void prevHistory()
111{
112 TT.history = TT.history->prev;
113 strcpy(toybuf, TT.history->data);
114 TT.x = strlen(toybuf);
115 updateLine();
116}
117
118static void quit()
119{
120 handle_keys_quit();
121}
122
123static void rightChar()
124{
125 TT.x++;
126 updateLine();
127}
128
129static void startOfLine()
130{
131 TT.x = 0;
132 updateLine();
133}
134
135// The key to command mappings, Emacs style.
136static const struct keyCommand simpleEmacsKeys[] =
137{
138 {"BS", backSpaceChar},
139 {"Del", deleteChar},
140 {"^D", deleteChar},
141 {"Return", doCommand},
142 {"Enter", doCommand},
143 {"Down", nextHistory},
144 {"^N", nextHistory},
145 {"End", endOfLine},
146 {"^E", endOfLine},
147 {"Left", leftChar},
148 {"^B", leftChar},
149 {"^X^C", quit},
150 {"^C", quit},
151 {"Right", rightChar},
152 {"^F", rightChar},
153 {"Home", startOfLine},
154 {"^A", startOfLine},
155 {"Up", prevHistory},
156 {"^P", prevHistory}
157};
158
159// Callback for incoming sequences from the terminal.
160static int handleEvent(long extra, struct keyevent *event)
161{
162 switch (event->type)
163 {
164 case HK_KEYS :
165 {
166 int j, l = strlen(event->sequence);
167
168 // Search for a key sequence bound to a command.
169 for (j = 0; j < ARRAY_LEN(simpleEmacsKeys); j++)
170 {
171 if (strncmp(simpleEmacsKeys[j].key, event->sequence, l) == 0)
172 {
173 // If it's a partial match, keep accumulating them.
174 if (strlen(simpleEmacsKeys[j].key) != l)
175 return 0;
176 else
177 {
178 if (simpleEmacsKeys[j].handler) simpleEmacsKeys[j].handler();
179 return 1;
180 }
181 }
182 }
183
184 // See if it's ordinary keys.
185 // NOTE - with vi style ordinary keys can be commands,
186 // but they would be found by the command check above first.
187 if (!event->isTranslated)
188 {
189 if (TT.x < sizeof(toybuf))
190 {
191 int j, l = strlen(event->sequence);
192
193 for (j = strlen(toybuf); j >= TT.x; j--)
194 toybuf[j + l] = toybuf[j];
195 for (j = 0; j < l; j++)
196 toybuf[TT.x + j] = event->sequence[j];
197 TT.x += l;
198 updateLine();
199 }
200 }
201 break;
202 }
203
204 case HK_CSI :
205 {
206 // Is it a cursor location report?
207 if (strcmp("R", event->sequence) == 0)
208 {
209 // Parameters are cursor line and column.
210 // NOTE - This may be sent at other times, not just during terminal resize.
211 // We are assuming here that it's a resize.
212 // The defaults are 1, which get ignored by the heuristic below.
213 int r = event->params[0], c = event->params[1];
214
215 // Check it's not an F3 key variation, coz some of them use
216 // the same CSI function command.
217 // This is a heuristic, we are checking against an unusable terminal size.
218 if ((2 == event->count) && (8 < r) && (8 < c))
219 {
220 TT.h = r;
221 TT.w = c;
222 updateLine();
223 }
224 break;
225 }
226 }
227
228 default : break;
229 }
230
231 // Tell handle_keys to drop it, coz we dealt with it, or it's not one of ours.
232 return 1;
233}
234
235void dumbsh_main(void)
236{
237 struct termios termIo, oldTermIo;
238 char *t = getenv("HOME");
239 int fd;
240
241 // Load bash history.
242 t = xmprintf("%s/%s", t ? t : "", ".bash_history");
243 if (-1 != (fd = open(t, O_RDONLY)))
244 {
245 while ((t = get_line(fd))) TT.history = dlist_add(&TT.history, t);
246 close(fd);
247 }
248 if (!TT.history)
249 TT.history = dlist_add(&TT.history, "");
250
251 // Grab the old terminal settings and save it.
252 tcgetattr(0, &oldTermIo);
253 tcflush(0, TCIFLUSH);
254 termIo = oldTermIo;
255
256 // Mould the terminal to our will.
257 // In this example we are turning off all the terminal smarts, but real code
258 // might not want that.
259 termIo.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL
260 | IUCLC | IXON | IXOFF | IXANY);
261 termIo.c_oflag &= ~OPOST;
262 termIo.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | TOSTOP | ICANON | ISIG
263 | IEXTEN);
264 termIo.c_cflag &= ~(CSIZE | PARENB);
265 termIo.c_cflag |= CS8;
266 termIo.c_cc[VTIME]=0; // deciseconds.
267 termIo.c_cc[VMIN]=1;
268 tcsetattr(0, TCSANOW, &termIo);
269
270 // Let the mouldy old terminal mold us.
271 TT.w = 80;
272 TT.h = 24;
273 terminal_size(&TT.w, &TT.h);
274
275 // Let's rock!
276 updateLine();
277 handle_keys(0, handleEvent);
278
279 // Clean up.
280 tcsetattr(0, TCSANOW, &oldTermIo);
281 puts("");
282 fflush(stdout);
283}
diff --git a/src/fcgi_SC.c b/src/fcgi_SC.c
new file mode 100644
index 0000000..36aba77
--- /dev/null
+++ b/src/fcgi_SC.c
@@ -0,0 +1,13 @@
1/* fcgi_SC.h - Generic fcgi handler, coz the others all suck.
2 *
3 * Copyright 2020 David Seikel <onefang@sledjhamr.org>
4 */
5
6// I use camelCaseNames internally, instead of underscore_names as is preferred
7// in the rest of toybox. A small limit of 80 characters per source line infers
8// shorter names should be used. CamelCaseNames are shorter. Externally visible
9// stuff is underscore_names as usual. Plus, I'm used to camelCaseNames, my
10// fingers twitch that way.
11
12#include "toys.h"
13#include "fcgi_SC.h"
diff --git a/src/fcgi_SC.h b/src/fcgi_SC.h
new file mode 100644
index 0000000..2b3fa65
--- /dev/null
+++ b/src/fcgi_SC.h
@@ -0,0 +1,136 @@
1/* fcgi_SC.h - Generic fcgi handler, coz the oters all suck.
2 *
3 * Copyright 2020 David Seikel <onefang@sledjhamr.org>
4 */
5
6enum fcgiEventType{
7 FSC_CSI,
8 FSC_KEYS,
9 FSC_MOUSE,
10 FSC_RAW
11};
12
13struct fcgiEvent {
14 enum fcgiEventType type; // The type of this event.
15 char *sequence; // Either a translated sequence, or raw bytes.
16 int isTranslated; // Whether or not sequence is translated.
17 int count; // Number of entries in params.
18 int *params; // For CSI events, the decoded parameters.
19};
20
21
22
23
24// From the spec.
25
26/*
27 * Listening socket file number
28 */
29#define FCGI_LISTENSOCK_FILENO 0
30
31typedef struct {
32 unsigned char version;
33 unsigned char type;
34 unsigned char requestIdB1;
35 unsigned char requestIdB0;
36 unsigned char contentLengthB1;
37 unsigned char contentLengthB0;
38 unsigned char paddingLength;
39 unsigned char reserved;
40} FCGI_Header;
41
42/*
43 * Number of bytes in a FCGI_Header. Future versions of the protocol
44 * will not reduce this number.
45 */
46#define FCGI_HEADER_LEN 8
47
48/*
49 * Value for version component of FCGI_Header
50 */
51#define FCGI_VERSION_1 1
52
53/*
54 * Values for type component of FCGI_Header
55 */
56#define FCGI_BEGIN_REQUEST 1
57#define FCGI_ABORT_REQUEST 2
58#define FCGI_END_REQUEST 3
59#define FCGI_PARAMS 4
60#define FCGI_STDIN 5
61#define FCGI_STDOUT 6
62#define FCGI_STDERR 7
63#define FCGI_DATA 8
64#define FCGI_GET_VALUES 9
65#define FCGI_GET_VALUES_RESULT 10
66#define FCGI_UNKNOWN_TYPE 11
67#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
68
69/*
70 * Value for requestId component of FCGI_Header
71 */
72#define FCGI_NULL_REQUEST_ID 0
73
74typedef struct {
75 unsigned char roleB1;
76 unsigned char roleB0;
77 unsigned char flags;
78 unsigned char reserved[5];
79} FCGI_BeginRequestBody;
80
81typedef struct {
82 FCGI_Header header;
83 FCGI_BeginRequestBody body;
84} FCGI_BeginRequestRecord;
85
86/*
87 * Mask for flags component of FCGI_BeginRequestBody
88 */
89#define FCGI_KEEP_CONN 1
90
91/*
92 * Values for role component of FCGI_BeginRequestBody
93 */
94#define FCGI_RESPONDER 1
95#define FCGI_AUTHORIZER 2
96#define FCGI_FILTER 3
97
98typedef struct {
99 unsigned char appStatusB3;
100 unsigned char appStatusB2;
101 unsigned char appStatusB1;
102 unsigned char appStatusB0;
103 unsigned char protocolStatus;
104 unsigned char reserved[3];
105} FCGI_EndRequestBody;
106
107typedef struct {
108 FCGI_Header header;
109 FCGI_EndRequestBody body;
110} FCGI_EndRequestRecord;
111
112/*
113 * Values for protocolStatus component of FCGI_EndRequestBody
114 */
115#define FCGI_REQUEST_COMPLETE 0
116#define FCGI_CANT_MPX_CONN 1
117#define FCGI_OVERLOADED 2
118#define FCGI_UNKNOWN_ROLE 3
119
120/*
121 * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
122 */
123#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
124#define FCGI_MAX_REQS "FCGI_MAX_REQS"
125#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
126
127typedef struct {
128 unsigned char type;
129 unsigned char reserved[7];
130} FCGI_UnknownTypeBody;
131
132typedef struct {
133 FCGI_Header header;
134 FCGI_UnknownTypeBody body;
135} FCGI_UnknownTypeRecord;
136
diff --git a/src/git-sub-modules/README b/src/git-sub-modules/README
new file mode 100644
index 0000000..c7a111b
--- /dev/null
+++ b/src/git-sub-modules/README
@@ -0,0 +1,2 @@
1Put git sub modules here, and then add code to BuildIt.sh to move them to
2where they are needed, or link them.
diff --git a/src/handlekeys.c b/src/handlekeys.c
new file mode 100644
index 0000000..8bae529
--- /dev/null
+++ b/src/handlekeys.c
@@ -0,0 +1,445 @@
1/* handlekeys.c - Generic terminal input handler.
2 *
3 * Copyright 2012 David Seikel <won_fang@yahoo.com.au>
4 */
5
6// I use camelCaseNames internally, instead of underscore_names as is preferred
7// in the rest of toybox. A small limit of 80 characters per source line infers
8// shorter names should be used. CamelCaseNames are shorter. Externally visible
9// stuff is underscore_names as usual. Plus, I'm used to camelCaseNames, my
10// fingers twitch that way.
11
12#include "toys.h"
13#include "handlekeys.h"
14
15struct key
16{
17 char *code;
18 char *name;
19};
20
21// This table includes some variations I have found on some terminals.
22// http://rtfm.etla.org/xterm/ctlseq.html has a useful guide.
23// TODO - Don't think I got all the linux console or xterm variations.
24// TODO - Add more shift variations, plus Ctrl & Alt variations when needed.
25// TODO - tmux messes with the shift function keys somehow.
26// TODO - Add other miscelany that does not use an escape sequence.
27
28// This is sorted by type, though there is some overlap.
29// Human typing speeds wont need fast searching speeds on this small table.
30// So simple wins out over speed, and sorting by terminal type wins
31// the simple test.
32static struct key keys[] =
33{
34 // Control characters.
35 // Commented out coz it's the C string terminator, and may confuse things.
36 //{"\x00", "^@"}, // NUL
37 {"\x01", "^A"}, // SOH Apparently sometimes sent as Home.
38 {"\x02", "^B"}, // STX
39 {"\x03", "^C"}, // ETX SIGINT Emacs and vi.
40 {"\x04", "^D"}, // EOT EOF Emacs, joe, and nano.
41 {"\x05", "^E"}, // ENQ Apparently sometimes sent as End
42 {"\x06", "^F"}, // ACK
43 {"\x07", "^G"}, // BEL
44 {"\x08", "Del"}, // BS Delete key, usually.
45 {"\x09", "Tab"}, // HT
46 {"\x0A", "Enter"}, // LF Roxterm translates Ctrl-M to this.
47 {"\x0B", "^K"}, // VT
48 {"\x0C", "^L"}, // FF
49 {"\x0D", "Return"}, // CR Other Enter/Return key, usually.
50 {"\x0E", "^N"}, // SO
51 {"\x0F", "^O"}, // SI DISCARD
52 {"\x10", "^P"}, // DLE
53 {"\x11", "^Q"}, // DC1 SIGCONT Vi.
54 {"\x12", "^R"}, // DC2
55 {"\x13", "^S"}, // DC3 SIGSTOP can't be caught. Emacs and vi.
56 {"\x14", "^T"}, // DC4 SIGINFO STATUS
57 {"\x15", "^U"}, // NAK KILL character
58 {"\x16", "^V"}, // SYN LNEXT
59 {"\x17", "^W"}, // ETB WERASE
60 {"\x18", "^X"}, // CAN KILL character
61 {"\x19", "^Y"}, // EM DSUSP SIGTSTP
62 {"\x1A", "^Z"}, // SUB SIGTSTP
63 // Commented out coz it's the ANSI start byte in the below multibyte keys.
64 // Handled in the code with a timeout.
65 //{"\x1B", "Esc"}, // ESC Esc key.
66 {"\x1C", "^\\"}, // FS SIGQUIT
67 {"\x1D", "^]"}, // GS
68 {"\x1E", "^^"}, // RS
69 {"\x1F", "^_"}, // US
70 {"\x7F", "BS"}, // Backspace key, usually. Ctrl-? perhaps?
71 // Commented out for the same reason Esc is.
72 //{"\x9B", "CSI"}, // CSI The eight bit encoding of "Esc [".
73
74 // "Usual" xterm CSI sequences, with ";1" omitted for no modifiers.
75 // Even though we have a proper CSI parser,
76 // these should still be in this table. Coz we would need a table anyway
77 // in the CSI parser, so might as well keep them with the others.
78 // Also, less code, no need to have a separate scanner for that other table.
79 {"\x9B\x31~", "Home"}, // Duplicate, think I've seen this somewhere.
80 {"\x9B\x32~", "Ins"},
81 {"\x9B\x33~", "Del"},
82 {"\x9B\x34~", "End"}, // Duplicate, think I've seen this somewhere.
83 {"\x9B\x35~", "PgUp"},
84 {"\x9B\x36~", "PgDn"},
85 {"\x9B\x37~", "Home"},
86 {"\x9B\x38~", "End"},
87 {"\x9B\x31\x31~", "F1"},
88 {"\x9B\x31\x32~", "F2"},
89 {"\x9B\x31\x33~", "F3"},
90 {"\x9B\x31\x34~", "F4"},
91 {"\x9B\x31\x35~", "F5"},
92 {"\x9B\x31\x37~", "F6"},
93 {"\x9B\x31\x38~", "F7"},
94 {"\x9B\x31\x39~", "F8"},
95 {"\x9B\x32\x30~", "F9"},
96 {"\x9B\x32\x31~", "F10"},
97 {"\x9B\x32\x33~", "F11"},
98 {"\x9B\x32\x34~", "F12"},
99
100 // As above, ";2" means shift modifier.
101 {"\x9B\x31;2~", "Shift Home"},
102 {"\x9B\x32;2~", "Shift Ins"},
103 {"\x9B\x33;2~", "Shift Del"},
104 {"\x9B\x34;2~", "Shift End"},
105 {"\x9B\x35;2~", "Shift PgUp"},
106 {"\x9B\x36;2~", "Shift PgDn"},
107 {"\x9B\x37;2~", "Shift Home"},
108 {"\x9B\x38;2~", "Shift End"},
109 {"\x9B\x31\x31;2~", "Shift F1"},
110 {"\x9B\x31\x32;2~", "Shift F2"},
111 {"\x9B\x31\x33;2~", "Shift F3"},
112 {"\x9B\x31\x34;2~", "Shift F4"},
113 {"\x9B\x31\x35;2~", "Shift F5"},
114 {"\x9B\x31\x37;2~", "Shift F6"},
115 {"\x9B\x31\x38;2~", "Shift F7"},
116 {"\x9B\x31\x39;2~", "Shift F8"},
117 {"\x9B\x32\x30;2~", "Shift F9"},
118 {"\x9B\x32\x31;2~", "Shift F10"},
119 {"\x9B\x32\x33;2~", "Shift F11"},
120 {"\x9B\x32\x34;2~", "Shift F12"},
121
122 // "Normal" Some terminals are special, and it seems they only have
123 // four function keys.
124 {"\x9B\x41", "Up"},
125 {"\x9B\x42", "Down"},
126 {"\x9B\x43", "Right"},
127 {"\x9B\x44", "Left"},
128 {"\x9B\x46", "End"},
129 {"\x9BH", "Home"},
130 {"\x9BP", "F1"},
131 {"\x9BQ", "F2"},
132 {"\x9BR", "F3"},
133 {"\x9BS", "F4"},
134 {"\x9B\x31;2P", "Shift F1"},
135 {"\x9B\x31;2Q", "Shift F2"},
136 {"\x9B\x31;2R", "Shift F3"},
137 {"\x9B\x31;2S", "Shift F4"},
138
139 // "Application" Esc O is known as SS3
140 {"\x1BOA", "Up"},
141 {"\x1BOB", "Down"},
142 {"\x1BOC", "Right"},
143 {"\x1BOD", "Left"},
144 {"\x1BOF", "End"},
145 {"\x1BOH", "Home"},
146 {"\x1BOn", "Del"},
147 {"\x1BOp", "Ins"},
148 {"\x1BOq", "End"},
149 {"\x1BOw", "Home"},
150 {"\x1BOP", "F1"},
151 {"\x1BOQ", "F2"},
152 {"\x1BOR", "F3"},
153 {"\x1BOS", "F4"},
154 {"\x1BOT", "F5"},
155 // These two conflict with the above four function key variations.
156 {"\x9BR", "F6"},
157 {"\x9BS", "F7"},
158 {"\x9BT", "F8"},
159 {"\x9BU", "F9"},
160 {"\x9BV", "F10"},
161 {"\x9BW", "F11"},
162 {"\x9BX", "F12"},
163
164 // Can't remember, but saw them somewhere.
165 {"\x1BO1;2P", "Shift F1"},
166 {"\x1BO1;2Q", "Shift F2"},
167 {"\x1BO1;2R", "Shift F3"},
168 {"\x1BO1;2S", "Shift F4"},
169};
170
171static volatile sig_atomic_t sigWinch;
172static int stillRunning;
173
174static void handleSIGWINCH(int signalNumber)
175{
176 sigWinch = 1;
177}
178
179void handle_keys(long extra, int (*handle_event)(long extra, struct keyevent *event))
180{
181 struct keyevent event;
182 fd_set selectFds;
183 struct timespec timeOut;
184 struct sigaction sigAction, oldSigAction;
185 sigset_t signalMask;
186 char buffer[20], sequence[20];
187 int buffIndex = 0, pendingEsc = 0;
188
189 buffer[0] = 0;
190 sequence[0] = 0;
191
192 // Terminals send the SIGWINCH signal when they resize.
193 memset(&sigAction, 0, sizeof(sigAction));
194 sigAction.sa_handler = handleSIGWINCH;
195 sigAction.sa_flags = SA_RESTART; // Useless if we are using poll.
196 if (sigaction(SIGWINCH, &sigAction, &oldSigAction))
197 perror_exit("can't set signal handler for SIGWINCH");
198 sigemptyset(&signalMask);
199 sigaddset(&signalMask, SIGWINCH);
200
201 // TODO - OS buffered keys might be a problem, but we can't do the
202 // usual timestamp filter for now.
203
204 stillRunning = 1;
205 while (stillRunning)
206 {
207 int j, p, csi = 0;
208
209 // Apparently it's more portable to reset these each time.
210 FD_ZERO(&selectFds);
211 FD_SET(0, &selectFds);
212 timeOut.tv_sec = 0; timeOut.tv_nsec = 100000000; // One tenth of a second.
213
214 // We got a "terminal size changed" signal, ask the terminal
215 // how big it is now.
216 if (sigWinch)
217 {
218 // Send - save cursor position, down 999, right 999,
219 // request cursor position, restore cursor position.
220 fputs("\x1B[s\x1B[999C\x1B[999B\x1B[6n\x1B[u", stdout);
221 fflush(stdout);
222 sigWinch = 0;
223 }
224
225 // TODO - Should only ask for a time out after we get an Escape, or
226 // the user requested time ticks.
227 // I wanted to use poll, but that would mean using ppoll, which is
228 // Linux only, and involves defining swear words to get it.
229 p = pselect(0 + 1, &selectFds, NULL, NULL, &timeOut, &signalMask);
230 if (0 > p)
231 {
232 if (EINTR == errno)
233 continue;
234 perror_exit("poll");
235 }
236 else if (0 == p) // A timeout, trigger a time event.
237 {
238 if (pendingEsc)
239 {
240 // After a short delay to check, this is a real Escape key,
241 // not part of an escape sequence, so deal with it.
242 strcat(sequence, "Esc");
243 buffer[0] = buffIndex = 0;
244 }
245 // TODO - Call some sort of timer tick callback. This wont be
246 // a precise timed event, but don't think we need one.
247 }
248 else if ((0 < p) && FD_ISSET(0, &selectFds))
249 {
250 j = xread(0, &buffer[buffIndex], sizeof(buffer) - (buffIndex + 1));
251 if (j == 0) // End of file.
252 stillRunning = 0;
253 else
254 {
255 buffIndex += j;
256 buffer[buffIndex] = 0;
257
258 // Send raw keystrokes, mostly for things like showkey.
259 event.type = HK_RAW;
260 event.sequence = buffer;
261 event.isTranslated = 0;
262 handle_event(extra, &event);
263
264 if (sizeof(buffer) < (buffIndex + 1)) // Ran out of buffer.
265 {
266 buffer[buffIndex] = 0;
267 fprintf(stderr, "Full buffer - %s -> %s\n", buffer, sequence);
268 for (j = 0; buffer[j]; j++)
269 fprintf(stderr, "(%x) %c, ", (int) buffer[j], buffer[j]);
270 fflush(stderr);
271 buffer[0] = buffIndex = 0;
272 }
273 }
274 }
275
276 // Check for lone Esc first, wait a bit longer if it is.
277 pendingEsc = ((0 == buffer[1]) && ('\x1B' == buffer[0]));
278 if (pendingEsc) continue;
279
280 // Check if it's a CSI before we check for the known key sequences.
281 // C29B is the UTF8 encoding of CSI.
282 // In all cases we reduce CSI to 9B to keep the keys table shorter.
283 if ((('\x1B' == buffer[0]) && ('[' == buffer[1]))
284 || (('\xC2' == buffer[0]) && ('\x9B' == buffer[1])))
285 {
286 buffer[0] = '\x9B';
287 for (j = 1; buffer[j]; j++)
288 buffer[j] = buffer[j + 1];
289 buffIndex--;
290 }
291 csi = ('\x9B' == buffer[0]);
292
293 // Check for known key sequences.
294 // For a real timeout checked Esc, buffer is now empty, so this for loop
295 // wont find it anyway. While it's true we could avoid it by checking,
296 // the user already had to wait for a time out, and this loop wont take THAT long.
297 for (j = 0; j < ARRAY_LEN(keys); j++)
298 {
299 if (strcmp(keys[j].code, buffer) == 0)
300 {
301 strcat(sequence, keys[j].name);
302 buffer[0] = buffIndex = 0;
303 csi = 0;
304 break;
305 }
306 }
307
308 // Find out if it's a CSI sequence that's not in the known key sequences.
309 if (csi)
310 {
311 /* ECMA-048 section 5.2 defines this, and is unreadable.
312 * So I'll include some notes here that tries to simplify that.
313 *
314 * The CSI format is - CSI [private] n1 ; n2 [extra] final
315 * Each of those parts, except for the initial CSI bytes, is an ordinary
316 * ASCII character.
317 *
318 * The optional [private] part is one of these characters "<=>?".
319 * If the first byte is one of these, then this is a private command, if
320 * it's one of the other n1 ones, it's not private.
321 *
322 * Next is a semi colon separated list of parameters (n1, n2, etc), which
323 * can be any characters from this set "01234567890:;<=>?". What the non
324 * digit ones mean is up to the command. Parameters can be left out, but
325 * their defaults are command dependant.
326 *
327 * Next is an optional [extra] part from this set of characters
328 * "!#$%&'()*+,-./", which includes double quotes. Can be many of these,
329 * likely isn't.
330 *
331 * Finally is the "final" from this set of characters "@[\]^_`{|}~", plus
332 * upper and lower case letters. It's private if it's one of these
333 * "pqrstuvwxyz{|}~". Though the "private" ~ is used for key codes.
334 *
335 * A full CSI command is the private, extra, and final parts.
336 *
337 * Any C0 controls, DEL (0x7f), or higher characters are undefined.
338 * TODO - So abort the current CSI and start from scratch on one of those.
339 */
340
341 if ('M' == buffer[1])
342 {
343 // We have a mouse report, which is CSI M ..., where the rest is
344 // binary encoded, more or less. Not fitting into the CSI format.
345 // To make things worse, can't tell how long this will be.
346 // So leave it up to the caller to tell us if they used it.
347 event.type = HK_MOUSE;
348 event.sequence = buffer;
349 event.isTranslated = 0;
350 if (handle_event(extra, &event))
351 {
352 buffer[0] = buffIndex = 0;
353 sequence[0] = 0;
354 }
355 }
356 else
357 {
358 char *t, csFinal[8];
359 int csIndex = 1, csParams[8];
360
361 csFinal[0] = 0;
362 p = 0;
363
364 // Unspecified params default to a value that is command dependant.
365 // However, they will never be negative, so we can use -1 to flag
366 // a default value.
367 for (j = 0; j < ARRAY_LEN(csParams); j++)
368 csParams[j] = -1;
369
370 // Check for the private bit.
371 if (index("<=>?", buffer[1]))
372 {
373 csFinal[0] = buffer[1];
374 csFinal[1] = 0;
375 csIndex++;
376 }
377
378 // Decode parameters.
379 j = csIndex;
380 do
381 {
382 // So we know when we get to the end of parameter space.
383 t = index("01234567890:;<=>?", buffer[j + 1]);
384 // See if we passed a paremeter.
385 if ((';' == buffer[j]) || (!t))
386 {
387 // Only stomp on the ; if it's really the ;.
388 if (t)
389 buffer[j] = 0;
390 // Empty parameters are default parameters, so only deal with
391 // non defaults.
392 if (';' != buffer[csIndex] || (!t))
393 {
394 // TODO - Might be ":" in the number somewhere, but we are not
395 // expecting any in anything we do.
396 csParams[p] = atoi(&buffer[csIndex]);
397 }
398 p++;
399 csIndex = j + 1;
400 }
401 j++;
402 }
403 while (t);
404
405 // Check if we got the final byte, and send it to the callback.
406 strcat(csFinal, &buffer[csIndex]);
407 t = csFinal + strlen(csFinal) - 1;
408 if (('\x40' <= (*t)) && ((*t) <= '\x7e'))
409 {
410 event.type = HK_CSI;
411 event.sequence = csFinal;
412 event.isTranslated = 1;
413 event.count = p;
414 event.params = csParams;
415 handle_event(extra, &event);
416 buffer[0] = buffIndex = 0;
417 sequence[0] = 0;
418 }
419 }
420 }
421
422 // Pass the result to the callback.
423 if (sequence[0] || buffer[0])
424 {
425 char b[strlen(sequence) + strlen(buffer) + 1];
426
427 sprintf(b, "%s%s", sequence, buffer);
428 event.type = HK_KEYS;
429 event.sequence = b;
430 event.isTranslated = (0 != sequence[0]);
431 if (handle_event(extra, &event))
432 {
433 buffer[0] = buffIndex = 0;
434 sequence[0] = 0;
435 }
436 }
437 }
438
439 sigaction(SIGWINCH, &oldSigAction, NULL);
440}
441
442void handle_keys_quit()
443{
444 stillRunning = 0;
445}
diff --git a/src/handlekeys.h b/src/handlekeys.h
new file mode 100644
index 0000000..868183f
--- /dev/null
+++ b/src/handlekeys.h
@@ -0,0 +1,76 @@
1/* handlekeys.h - Generic terminal input handler.
2 *
3 * Copyright 2012 David Seikel <won_fang@yahoo.com.au>
4 */
5
6enum keyeventtype{
7 HK_CSI,
8 HK_KEYS,
9 HK_MOUSE,
10 HK_RAW
11};
12
13struct keyevent {
14 enum keyeventtype type; // The type of this event.
15 char *sequence; // Either a translated sequence, or raw bytes.
16 int isTranslated; // Whether or not sequence is translated.
17 int count; // Number of entries in params.
18 int *params; // For CSI events, the decoded parameters.
19};
20
21/* An input loop that handles keystrokes and terminal CSI commands.
22 *
23 * Reads stdin, trying to translate raw keystrokes into something more readable.
24 * See the keys[] array at the top of handlekeys.c for what byte sequences get
25 * translated into what key names. See dumbsh.c for an example of usage.
26 * A 0.1 second delay is used to detect the Esc key being pressed, and not Esc
27 * being part of a raw keystroke.
28 *
29 * handle_keys also tries to decode CSI commands that terminals can send.
30 * Some keystrokes are CSI commands, but those are translated as key sequences
31 * instead of CSI commands.
32 *
33 * handle_keys also sets up a SIGWINCH handler to catch terminal resizes,
34 * and sends a request to the terminal to report it's current size when it gets
35 * a SIGWINCH. This is the main reason for HK_CSI, as those reports are
36 * sent as CSI. It's still up to the user code to recognise and deal with the
37 * terminal resize response, but at least it's nicely decoded for you.
38 *
39 * Arguments -
40 * extra - arbitrary data that gets passed back to the callbacks.
41 * handle_event - a callback to handle sequences.
42 *
43 * handle_event is called when a complete sequence has been accumulated. It is
44 * passed a keyevent structure. The type member of that structure determines
45 * what sort of event this is. What's in the rest of the keyevent depends on
46 * the type -
47 *
48 * HK_CSI
49 * sequence is the fully decoded CSI command, including the private and intermediate characters.
50 * isTranslated is 1, since the CSI command has been translated.
51 * count is the count of translated CSI parameters.
52 * params is an array of translateted CSI parameters.
53 * Empty parameters are set to -1, coz -1 parameters are not legal,
54 * and empty ones should default to something that is command dependant.
55 *
56 * HK_KEYS
57 * sequence the keystrokes as ASCII, either translated or not.
58 * isTranslated if 0, then sequence is ordinary keys, otherwise
59 * sequence is the names of keys, from the keys[] array.
60 * count and params are not used.
61 *
62 * For HK_KEYS handle_event should return 1 if the sequence has been dealt with,
63 * or ignored. It should return 0, if handle_keys should keep adding more
64 * translated keystroke sequences on the end, and try again later.
65 * 0 should really only be used if it's a partial match, and we need more
66 * keys in the sequence to make a full match.
67 *
68 * HK_MOUSE
69 * sequence is the raw bytes of the mouse report. The rest are not used.
70 *
71 */
72void handle_keys(long extra, int (*handle_event)(long extra, struct keyevent *event));
73
74
75/* Call this when you want handle_keys to return. */
76void handle_keys_quit();
diff --git a/src/script.lua b/src/script.lua
new file mode 100644
index 0000000..1e4b909
--- /dev/null
+++ b/src/script.lua
@@ -0,0 +1,18 @@
1-- script.lua
2
3-- This works coz LuaJIT automatically loads the jit module.
4if type(jit) == 'table' then
5 io.write('script.lua is being run by ' .. jit.version .. ' under ' .. jit.os .. ' on a ' .. jit.arch .. '\n')
6else
7 io.write('script.lua is being run by Lua version ' .. _VERSION .. '\n')
8end
9
10-- Receives a table, returns the sum of its components.
11io.write("The table the script received has:\n");
12x = 0
13for i = 1, #foo do
14 print(i, foo[i])
15 x = x + foo[i]
16end
17io.write("Returning data back to C\n");
18return x
diff --git a/src/showkey.c b/src/showkey.c
new file mode 100644
index 0000000..de1f804
--- /dev/null
+++ b/src/showkey.c
@@ -0,0 +1,149 @@
1/* showkey.c - Shows the keys pressed.
2 *
3 * Copyright 2014 David Seikel <won_fang@yahoo.com.au>
4 *
5 * Not actually a standard, seems to be three different versions.
6 * The original kbd - http://kbd-project.org/
7 * The kbd fork console-tools - http://lct.sourceforge.net/
8 * A utility invented by Eric S. Raymond - http://catb.org/esr/showkey/
9
10USE_SHOWKEY(NEWTOY(showkey, "", TOYFLAG_USR|TOYFLAG_BIN))
11
12config SHOWKEY
13 bool "showkey"
14 default n
15 help
16 usage: showkey
17
18 Shows the keys pressed.
19*/
20
21#include "toys.h"
22#include "lib/handlekeys.h"
23
24typedef void (*eventHandler) (void);
25
26struct keyCommand
27{
28 char *key;
29 eventHandler handler;
30};
31
32GLOBALS(
33 unsigned h, w;
34 int x, y;
35)
36
37#define TT this.showkey
38
39
40static void quit()
41{
42 printf("Quitting.\r\n");
43 handle_keys_quit();
44}
45
46// The key to command mappings.
47static struct keyCommand simpleKeys[] =
48{
49 {"^C", quit}
50};
51
52// Callback for incoming sequences from the terminal.
53static int handleEvent(long extra, struct keyevent *event)
54{
55 int i;
56
57 switch (event->type)
58 {
59 case HK_RAW :
60 {
61 printf("RAW ");
62 for (i = 0; event->sequence[i]; i++)
63 {
64 printf("(%x) ", (int) event->sequence[i]);
65 if (32 > event->sequence[i])
66 printf("^%c, ", (int) event->sequence[i] + 'A' - 1);
67 else
68 printf("%c, ", (int) event->sequence[i]);
69 }
70 printf("-> ");
71 break;
72 }
73
74 case HK_KEYS :
75 {
76 int l = strlen(event->sequence);
77
78 if (event->isTranslated)
79 printf("TRANSLATED - ");
80 else
81 printf("KEY - ");
82 printf("%s\r\n", event->sequence);
83
84 // Search for a key sequence bound to a command.
85 for (i = 0; i < ARRAY_LEN(simpleKeys); i++)
86 {
87 if (strncmp(simpleKeys[i].key, event->sequence, l) == 0)
88 {
89 // If it's a partial match, keep accumulating them.
90 if (strlen(simpleKeys[i].key) != l)
91 return 0;
92 else
93 if (simpleKeys[i].handler) simpleKeys[i].handler();
94 }
95 }
96 break;
97 }
98
99 case HK_CSI :
100 {
101 // Is it a cursor location report?
102 if (strcmp("R", event->sequence) == 0)
103 {
104 printf("CSI cursor position - line %d, column %d\r\n", event->params[0], event->params[1]);
105 return 1;
106 }
107
108 printf("CSI command %s - ", event->sequence);
109 for (i = 0; i < event->count; i++)
110 printf("%d ", event->params[i]);
111 printf("\r\n");
112 break;
113 }
114
115 default : break;
116 }
117
118 return 1;
119}
120
121void showkey_main(void)
122{
123 struct termios termIo, oldTermIo;
124
125 // Grab the old terminal settings and save it.
126 tcgetattr(0, &oldTermIo);
127 tcflush(0, TCIFLUSH);
128 termIo = oldTermIo;
129
130 // Mould the terminal to our will.
131 // In this example we are turning off all the terminal smarts, but real code
132 // might not want that.
133 termIo.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL
134 | IUCLC | IXON | IXOFF | IXANY);
135 termIo.c_oflag &= ~OPOST;
136 termIo.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | TOSTOP | ICANON | ISIG
137 | IEXTEN);
138 termIo.c_cflag &= ~(CSIZE | PARENB);
139 termIo.c_cflag |= CS8;
140 termIo.c_cc[VTIME]=0; // deciseconds.
141 termIo.c_cc[VMIN]=1;
142 tcsetattr(0, TCSANOW, &termIo);
143
144 handle_keys(0, handleEvent);
145
146 tcsetattr(0, TCSANOW, &oldTermIo);
147 puts("");
148 fflush(stdout);
149}
diff --git a/src/sledjchisl.c b/src/sledjchisl.c
new file mode 100644
index 0000000..df24ad7
--- /dev/null
+++ b/src/sledjchisl.c
@@ -0,0 +1,1297 @@
1/* sledjchisl.c - opensim-SC management system.
2 *
3 * Copyright 2020 David Seikel <sledjchisl@sledjhamr.org>
4*/
5
6#include <fcgi_config.h>
7#ifdef _WIN32
8#include <process.h>
9#else
10extern char **environ;
11#endif
12#define NO_FCGI_DEFINES
13#include <fcgi_stdio.h>
14#undef NO_FCGI_DEFINES
15//#include "fcgiapp.h"
16
17#include <lua.h>
18#include <lualib.h>
19#include <lauxlib.h>
20#include <luajit.h>
21
22#include "fcgi_SC.h"
23#include "handlekeys.h"
24
25// Both my_config.h and fcgi_config.h define the same PACKAGE* variables, which we don't use anyway, soooo -
26//#undef PACKAGE
27//#undef PACKAGE_NAME
28//#undef PACKAGE_STRING
29//#undef PACKAGE_TARNAME
30//#undef PACKAGE_VERSION
31//#undef VERSION
32
33// https://mariadb.com/kb/en/about-mariadb-connector-c/ Official docs.
34// http://dev.mysql.com/doc/refman/5.5/en/c-api-function-overview.html MySQL docs.
35// http://zetcode.com/db/mysqlc/ MySQL tutorial.
36#include <my_global.h>
37#include <mysql.h>
38
39#include <qlibc.h>
40#include <extensions/qconfig.h>
41
42// Toybox's library has something really odd in it that causes MariaDB library to crash, no idea what.
43// I've copied the stuff I'm using, and that works.
44#include "toybox.h"
45
46
47//struct toy_context toys;
48char toybuf[4096];
49int isTmux = 0;
50int isWeb = 0;
51char *pwd = "";
52char *scRoot = "/opt/opensim_SC";
53char *scUser = "opensimsc";
54char *Tconsole = "SledjChisl";
55char *Tsocket = "caches/opensim-tmux.socket";
56char *Ttab = "SC";
57char *Tcmd = "tmux -S";
58char *webRoot = "/opt/opensim_SC/web";
59float loadAverageInc = 0.5;
60int simTimeOut = 45;
61
62char *logTypes[] =
63{
64 "CRITICAL",
65 "ERROR",
66 "WARNING",
67 "TIMEOUT",
68 "INFO",
69 "DEBUG",
70};
71
72#define DATE_TIME_LEN 42
73void logMe(int v, char *format, ...)
74{
75 va_list va, va2;
76 int len;
77 char *ret;
78 struct timeval tv;
79 time_t curtime;
80 char date[DATE_TIME_LEN];
81
82 va_start(va, format);
83 va_copy(va2, va);
84 // How long is it?
85 len = vsnprintf(0, 0, format, va);
86 len++;
87 va_end(va);
88 // Allocate and do the sprintf()
89 ret = xmalloc(len);
90 vsnprintf(ret, len, format, va2);
91 va_end(va2);
92
93 gettimeofday(&tv, NULL);
94 curtime = tv.tv_sec;
95 strftime(date, DATE_TIME_LEN, "(%Z %z) %F %R", localtime(&curtime));
96
97 fprintf(stderr, "%s.%.6ld %s: %s\n", date, tv.tv_usec, logTypes[v], ret);
98 free(ret);
99}
100#define C(...) logMe(0, __VA_ARGS__)
101#define E(...) logMe(1, __VA_ARGS__)
102#define W(...) logMe(2, __VA_ARGS__)
103#define T(...) logMe(3, __VA_ARGS__)
104#define I(...) logMe(4, __VA_ARGS__)
105#define D(...) logMe(5, __VA_ARGS__)
106
107
108
109// In Lua 5.0 reference manual is a table traversal example at page 29.
110void PrintTable(lua_State *L)
111{
112 lua_pushnil(L);
113
114 while (lua_next(L, -2) != 0)
115 {
116 // Numbers can convert to strings, so check for numbers before checking for strings.
117 if (lua_isnumber(L, -1))
118 printf("%s = %f\n", lua_tostring(L, -2), lua_tonumber(L, -1));
119 else if (lua_isstring(L, -1))
120 printf("%s = '%s'\n", lua_tostring(L, -2), lua_tostring(L, -1));
121 else if (lua_istable(L, -1))
122 PrintTable(L);
123 lua_pop(L, 1);
124 }
125}
126
127
128int sendTmuxKeys(char *dest, char *keys)
129{
130 int ret = 0, i;
131 char *c = xmprintf("%s %s/%s send-keys -t %s:%s '%s'", Tcmd, scRoot, Tsocket, Tconsole, dest, keys);
132
133 i = system(c);
134 if (!WIFEXITED(i))
135 E("tmux send-keys command failed!");
136 free(c);
137 return ret;
138}
139
140int sendTmuxCmd(char *dest, char *cmd)
141{
142 int ret = 0, i;
143 char *c = xmprintf("%s %s/%s send-keys -t %s:'%s' '%s' Enter", Tcmd, scRoot, Tsocket, Tconsole, dest, cmd);
144
145 i = system(c);
146 if (!WIFEXITED(i))
147 E("tmux send-keys command failed!");
148 free(c);
149 return ret;
150}
151
152void waitTmuxText(char *dest, char *text)
153{
154 int i;
155 char *c = xmprintf("sleep 5; %s %s/%s capture-pane -t %s:'%s' -p | grep -E '%s' 2>&1 > /dev/null", Tcmd, scRoot, Tsocket, Tconsole, dest, text);
156
157 D("Waiting for '%s'.", text);
158 do
159 {
160 i = system(c);
161 if (!WIFEXITED(i))
162 {
163 E("tmux capture-pane command failed!");
164 break;
165 }
166 else if (0 == WEXITSTATUS(i))
167 break;
168 } while (1);
169
170 free(c);
171}
172
173float waitLoadAverage(float la, float extra, int timeout)
174{
175 struct sysinfo info;
176 struct timespec timeOut;
177 float l;
178 int to = timeout;
179
180 I("Sleeping until load average is below %.02f (%.02f + %.02f) or for %d seconds.", la + extra, la, extra, timeout);
181 clock_gettime(CLOCK_MONOTONIC, &timeOut);
182 to += timeOut.tv_sec;
183
184 do
185 {
186 msleep(5000);
187 sysinfo(&info);
188 l = info.loads[0]/65536.0;
189 clock_gettime(CLOCK_MONOTONIC, &timeOut);
190 timeout -= 5;
191 I("Tick, load average is %.02f, countdown %d seconds.", l, timeout);
192 } while (((la + extra) < l) && (timeOut.tv_sec < to));
193
194 return l;
195}
196
197
198typedef struct _simList simList;
199struct _simList
200{
201 int len, num;
202 char **sims;
203};
204
205static int filterSims(struct tb_dirtree *node)
206{
207 if (!node->parent) return TB_DIRTREE_RECURSE | TB_DIRTREE_SHUTUP;
208 if ((strncmp(node->name, "sim", 3) == 0) && ((strcmp(node->name, "sim_skeleton") != 0)))
209 {
210 simList *list = (simList *) node->parent->extra;
211
212 if ((list->num + 1) > list->len)
213 {
214 list->len = list->len + 1;
215 list->sims = xrealloc(list->sims, list->len * sizeof(char *));
216 }
217 list->sims[list->num] = xstrdup(node->name);
218 list->num++;
219 }
220 return 0;
221}
222
223simList *getSims()
224{
225 simList *sims = xmalloc(sizeof(simList));
226 memset(sims, 0, sizeof(simList));
227 memset(toybuf, 0, sizeof(toybuf));
228 snprintf(toybuf, sizeof(toybuf), "%s/config", scRoot);
229 struct tb_dirtree *new = tb_dirtree_add_node(0, toybuf, 0);
230 new->extra = (long) sims;
231 tb_dirtree_handle_callback(new, filterSims);
232 qsort(sims->sims, sims->num, sizeof(char *), qstrcmp);
233 tb_dirtree_free(new);
234 return sims;
235}
236
237
238static int filterInis(struct tb_dirtree *node)
239{
240 if (!node->parent) return TB_DIRTREE_RECURSE | TB_DIRTREE_SHUTUP | TB_DIRTREE_SAVE;
241 int l = strlen(node->name);
242 if (strncmp(&(node->name[l - 4]), ".ini", 4) == 0)
243 {
244 node->parent->extra = (long) node->name;
245 return TB_DIRTREE_ABORT;
246 }
247 return 0;
248}
249
250char *getSimName(char *sim)
251{
252 char *ret = NULL;
253 char *c = xmprintf("%s/config/%s", scRoot, sim);
254 struct tb_dirtree *new = tb_dirtree_add_node(0, c, 0);
255
256 tb_dirtree_handle_callback(new, filterInis);
257 if (new->extra)
258 {
259 char *temp = NULL;
260 regex_t pat;
261 regmatch_t m[2];
262 long len;
263 int fd;
264
265 c = xmprintf("%s/config/%s/%s", scRoot, sim, new->extra);
266 fd = xopenro(c);
267 xregcomp(&pat, "RegionName = \"(.+)\"", REG_EXTENDED);
268 do
269 {
270 // TODO - get_line() is slow, and wont help much with DOS and Mac line endings.
271 temp = get_line(fd);
272 if (temp)
273 {
274 if (!regexec(&pat, temp, 2, m, 0))
275 {
276 // Return first parenthesized subexpression as string.
277 if (pat.re_nsub > 0)
278 {
279 ret = xmprintf("%.*s", (int) (m[1].rm_eo - m[1].rm_so), temp + m[1].rm_so);
280 break;
281 }
282 }
283 }
284 } while (temp);
285 xclose(fd);
286 }
287 tb_dirtree_free(new);
288 return ret;
289}
290
291
292// Expects either "simXX" or "ROBUST".
293int checkSimIsRunning(char *sim)
294{
295 int ret = 0;
296 struct stat st;
297
298 // Check if it's running.
299 memset(toybuf, 0, sizeof(toybuf));
300 snprintf(toybuf, sizeof(toybuf), "%s/caches/%s.pid", scRoot, sim);
301 if (0 == stat(toybuf, &st))
302 {
303 int fd, i;
304 char *pid = NULL;
305
306 // Double check if it's REALLY running.
307 if ((fd = xopenro(toybuf)) == -1)
308 tb_perror_msg("xopenro(%s)", toybuf);
309 else
310 {
311 pid = get_line(fd);
312 if (NULL == pid)
313 tb_perror_msg("get_line(%s)", toybuf);
314 else
315 {
316 xclose(fd);
317
318 memset(toybuf, 0, sizeof(toybuf));
319 snprintf(toybuf, sizeof(toybuf), "ps -p %s --no-headers -o comm", pid);
320 i = system(toybuf);
321 if (WIFEXITED(i))
322 {
323 if (0 != WEXITSTATUS(i)) // No such pid.
324 {
325 memset(toybuf, 0, sizeof(toybuf));
326 snprintf(toybuf, sizeof(toybuf), "rm -f %s/caches/%s.pid", scRoot, sim);
327 D("%s", toybuf);
328 i = system(toybuf);
329 }
330 }
331 }
332 }
333 }
334
335 // Now check if it's really really running. lol
336 memset(toybuf, 0, sizeof(toybuf));
337 snprintf(toybuf, sizeof(toybuf), "%s/caches/%s.pid", scRoot, sim);
338 if (0 == stat(toybuf, &st))
339 ret = 1;
340
341 return ret;
342}
343
344static void PrintEnv(char *label, char **envp)
345{
346 FCGI_printf("%s:<br>\n<pre>\n", label);
347 for ( ; *envp != NULL; envp++)
348 {
349 FCGI_printf("%s\n", *envp);
350 }
351 FCGI_printf("</pre><p>\n");
352}
353
354static void printEnv(char **envp)
355{
356 for ( ; *envp != NULL; envp++)
357 {
358 D("%s", *envp);
359 }
360}
361
362
363my_ulonglong dbCount(MYSQL *db, char *table, char *where)
364{
365 my_ulonglong ret = 0;
366
367 memset(toybuf, 0, sizeof(toybuf));
368 if (NULL == where)
369 snprintf(toybuf, sizeof(toybuf), "SELECT Count(*) FROM %s", table);
370 else
371 snprintf(toybuf, sizeof(toybuf), "SELECT Count(*) FROM %s WHERE %s", table, where);
372
373 if (mysql_query(db, toybuf))
374 E("Query failed: %s", mysql_error(db));
375 else
376 {
377 MYSQL_RES *result = mysql_store_result(db);
378
379 if (!result)
380 E("Couldn't get results set from %s\n: %s", toybuf, mysql_error(db));
381 else
382 {
383 MYSQL_ROW row = mysql_fetch_row(result);
384 if (!row)
385 E("Couldn't get row from %s\n: %s", toybuf, mysql_error(db));
386 else
387 ret = atoll(row[0]);
388 mysql_free_result(result);
389 }
390 }
391
392 return ret;
393}
394
395my_ulonglong dbCountJoin(MYSQL *db, char *table, char *select, char *join, char *where, char *order)
396{
397 my_ulonglong ret = 0;
398
399 if (NULL == select)
400 select = "*";
401
402 memset(toybuf, 0, sizeof(toybuf));
403 if (NULL == where)
404 snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s", select, table, join);
405 else
406 snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s %s WHERE %s", select, table, join, where);
407
408 if (mysql_query(db, toybuf))
409 E("Query failed: %s", mysql_error(db));
410 else
411 {
412 MYSQL_RES *result = mysql_store_result(db);
413
414 if (!result)
415 E("Couldn't get results set from %s\n: %s", toybuf, mysql_error(db));
416 else
417 ret = mysql_num_rows(result);
418 mysql_free_result(result);
419 }
420
421 return ret;
422}
423
424qlist_t *dbSelect(MYSQL *db, char *table, char *select, char *join, char *where, char *order)
425{
426 qlist_t *ret = qlist(0);
427 if (NULL == select)
428 select = "*";
429
430 memset(toybuf, 0, sizeof(toybuf));
431 if (NULL == where)
432 snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s", select, table, join);
433 else
434 snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s %s WHERE %s", select, table, join, where);
435
436 if (mysql_query(db, toybuf))
437 E("Query failed: %s", mysql_error(db));
438 else
439 {
440 MYSQL_RES *result = mysql_store_result(db);
441
442 if (!result)
443 E("Couldn't get results set from %s\n: %s", toybuf, mysql_error(db));
444 else
445 {
446 MYSQL_FIELD *fields;
447
448 fields = mysql_fetch_fields(result);
449
450 if (!fields)
451 E("Faild fetching fields: %s", mysql_error(db));
452 else
453 {
454 unsigned int i, num_fields = mysql_num_fields(result);
455
456 MYSQL_ROW row;
457
458 while ((row = mysql_fetch_row(result)))
459 {
460 qhashtbl_t *flds = qhashtbl(0, 0);
461
462 for (i = 0; i < num_fields; i++)
463 {
464 flds->putstr(flds, fields[i].name, row[i]);
465 }
466 ret->addlast(ret, flds, sizeof(*flds));
467 }
468 }
469 }
470 mysql_free_result(result);
471 }
472
473 return ret;
474}
475
476void replaceStr(qhashtbl_t *ssi, char *key, char *value)
477{
478// char *tmp;
479
480// I think this is taken care of already.
481// if ((tmp = ssi->getstr(ssi, key, false)) != NULL)
482// free(tmp);
483 ssi->putstr(ssi, key, value);
484}
485
486void replaceLong(qhashtbl_t *ssi, char *key, my_ulonglong value)
487{
488 char *tmp = xmprintf("%lu", value);
489
490 replaceStr(ssi, key, tmp);
491 free(tmp);
492}
493
494
495float timeDiff(struct timeval *now, struct timeval *then)
496{
497 if (0 == gettimeofday(now, NULL))
498 {
499 struct timeval thisTime = { 0, 0 };
500 double result = 0.0;
501
502 thisTime.tv_sec = now->tv_sec;
503 thisTime.tv_usec = now->tv_usec;
504 if (thisTime.tv_usec < then->tv_usec)
505 {
506 thisTime.tv_sec--;
507 thisTime.tv_usec += 1000000;
508 }
509 thisTime.tv_usec -= then->tv_usec;
510 thisTime.tv_sec -= then->tv_sec;
511 result = ((double) thisTime.tv_usec) / ((double) 1000000.0);
512 result += thisTime.tv_sec;
513 return result;
514 }
515 else
516 return 0.0;
517}
518
519typedef struct _gridStats gridStats;
520struct _gridStats
521{
522 float next;
523 struct timeval last;
524 qhashtbl_t *stats;
525};
526
527gridStats *getStats(MYSQL *db, gridStats *stats)
528{
529 if (NULL == stats)
530 {
531 stats = xmalloc(sizeof(gridStats));
532 stats->next = 300;
533 gettimeofday(&(stats->last), NULL);
534 stats->stats = qhashtbl(0, 0);
535 stats->stats->putstr(stats->stats, "version", "SledjChisl FCGI Dev 0.1");
536 stats->stats->putstr(stats->stats, "grid", "my grid");
537 stats->stats->putstr(stats->stats, "uri", "http://localhost:8002/");
538
539 stats->stats->putstr(stats->stats, "gridOnline", "??");
540 }
541 else
542 {
543 static struct timeval thisTime;
544 if (stats->next > timeDiff(&thisTime, &(stats->last)))
545 return stats;
546 }
547
548 if (db)
549 {
550 I("Getting fresh grid stats.");
551 char *tmp;
552 my_ulonglong locIn = dbCount(db, "Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world.
553 my_ulonglong HGin = dbCount(db, "Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world.
554
555 // Collect stats about members.
556 replaceLong(stats->stats, "hgers", HGin);
557 replaceLong(stats->stats, "inworld", locIn - HGin);
558 tmp = xmprintf("GridExternalName != '%s'", stats->stats->getstr(stats->stats, "uri", false));
559 replaceLong(stats->stats, "outworld", dbCount(db, "hg_traveling_data", tmp));
560 free(tmp);
561 replaceLong(stats->stats, "members", dbCount(db, "UserAccounts", NULL));
562
563 // Count local and HG visitors for the last 30 and 60 days.
564 locIn = dbCountJoin(db, "GridUser", "GridUser.UserID", "INNER JOIN UserAccounts ON GridUser.UserID = UserAccounts.PrincipalID",
565 "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))", "");
566 HGin = dbCount(db, "GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))");
567 replaceLong(stats->stats, "locDay30", locIn);
568 replaceLong(stats->stats, "day30", HGin);
569 replaceLong(stats->stats, "HGday30", HGin - locIn);
570
571 locIn = dbCountJoin(db, "GridUser", "GridUser.UserID", "INNER JOIN UserAccounts ON GridUser.UserID = UserAccounts.PrincipalID",
572 "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 4838400))", "");
573 HGin = dbCount(db, "GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 4838400))");
574 replaceLong(stats->stats, "locDay60", locIn);
575 replaceLong(stats->stats, "day60", HGin);
576 replaceLong(stats->stats, "HGday60", HGin - locIn);
577
578 // Collect stats about sims.
579 replaceLong(stats->stats, "sims", dbCount(db, "regions", NULL));
580 replaceLong(stats->stats, "onlineSims", dbCount(db, "regions", "sizeX != 0"));
581 replaceLong(stats->stats, "varRegions", dbCount(db, "regions", "sizeX > 256 or sizeY > 256"));
582 replaceLong(stats->stats, "singleSims", dbCount(db, "regions", "sizeX = 256 and sizeY = 256"));
583 replaceLong(stats->stats, "offlineSims", dbCount(db, "regions", "sizeX = 0"));
584
585 // Calculate total size of all regions.
586 qlist_t *regions = dbSelect(db, "regions", "sizeX,sizeY", "", "sizeX != 0", "");
587 qlist_obj_t obj;
588 my_ulonglong simSize = 0;
589
590 memset((void *) &obj, 0, sizeof(obj)); // must be cleared before call
591 regions->lock(regions);
592 while (regions->getnext(regions, &obj, false) == true)
593 {
594 qhashtbl_t *row = (qhashtbl_t *) obj.data;
595 my_ulonglong x = 0, y = 0;
596
597 tmp = row->getstr(row, "sizeX", false);
598 if (NULL == tmp)
599 E("No regions.sizeX!");
600 else
601 x = atoll(tmp);
602 tmp = row->getstr(row, "sizeY", false);
603 if (NULL == tmp)
604 E("No regions.sizeY!");
605 else
606 y = atoll(tmp);
607 simSize += x * y;
608 }
609 regions->unlock(regions);
610 tmp = xmprintf("%lu", simSize);
611 stats->stats->putstr(stats->stats, "simsSize", tmp);
612 free(tmp);
613 gettimeofday(&(stats->last), NULL);
614 }
615 return stats;
616}
617
618
619enum fragmentType
620{
621 FT_TEXT,
622 FT_PARAM,
623 FT_LUA
624};
625
626typedef struct _fragment fragment;
627struct _fragment
628{
629 enum fragmentType type;
630 int length;
631 char *text;
632};
633
634typedef struct _HTMLfile HTMLfile;
635struct _HTMLfile
636{
637 struct timespec last;
638 qlist_t *fragments;
639};
640
641qhashtbl_t *HTMLfileCache = NULL;
642
643fragment *newFragment(enum fragmentType type, char *text, int len)
644{
645 fragment *frg = xmalloc(sizeof(fragment));
646 frg->type = type;
647 frg->length = len;
648 frg->text = xmalloc(len + 1);
649 memcpy(frg->text, text, len);
650 frg->text[len] = '\0';
651 return frg;
652}
653
654HTMLfile *checkHTMLcache(char *file)
655{
656 if (NULL == HTMLfileCache)
657 HTMLfileCache = qhashtbl(0, 0);
658
659 HTMLfile *ret = (HTMLfile *) HTMLfileCache->get(HTMLfileCache, file, NULL, false);
660 int fd = open(file, O_RDONLY);
661 size_t length = 0;
662 fragment *frg0, *frg1;
663
664 if (-1 == fd)
665 E("Failed to open %s", file);
666 else
667 {
668 struct stat sb;
669 if (fstat(fd, &sb) == -1)
670 E("Failed to stat %s", file);
671 else
672 {
673 if ((NULL != ret) && (ret->last.tv_sec < sb.st_mtim.tv_sec))
674 {
675 HTMLfileCache->remove(HTMLfileCache, file);
676 ret = NULL;
677 }
678
679 if (NULL == ret)
680 {
681 char *mm = MAP_FAILED;
682
683 ret = xmalloc(sizeof(HTMLfile));
684 ret->fragments = qlist(QLIST_THREADSAFE);
685 length = sb.st_size;
686 ret->last.tv_sec = sb.st_mtim.tv_sec;
687 ret->last.tv_nsec = sb.st_mtim.tv_nsec;
688
689 I("Loading web template %s", file);
690 D("Web template %s is %d bytes long.", file, length);
691
692 mm = mmap(NULL, length, PROT_READ, MAP_SHARED | MAP_POPULATE, fd, 0);
693 if (mm == MAP_FAILED)
694 E("Failed to mmap %s", file);
695 else
696 {
697 char *h;
698 int i, j = 0, k = 0, l, m;
699
700 // Scan for server side includes style markings.
701 for (i = 0; i < length; i++)
702 {
703 if (i + 5 < length)
704 {
705 if (('<' == mm[i]) && ('!' == mm[i + 1]) && ('-' == mm[i + 2]) && ('-' == mm[i + 3]) && ('#' == mm[i + 4])) // '<!--#'
706 {
707 m = i;
708 i += 5;
709 if (i < length)
710 {
711 if (('e' == mm[i]) && ('c' == mm[i + 1]) && ('h' == mm[i + 2]) && ('o' == mm[i + 3]) && (' ' == mm[i + 4])) // 'echo '
712 {
713 i += 5;
714 if (i + 5 < length)
715 {
716 if (('v' == mm[i]) && ('a' == mm[i + 1]) && ('r' == mm[i + 2]) && ('=' == mm[i + 3]) && ('"' == mm[i + 4])) // 'var="'
717 {
718 i += 5;
719 for (j = i; j < length; j++)
720 {
721 if ('"' == mm[j]) // '"'
722 {
723 frg1 = newFragment(FT_PARAM, &mm[i], j - i);
724 i = j + 1;
725 if (i + 4 < length)
726 {
727 if ((' ' == mm[i]) && ('-' == mm[i + 1]) && ('-' == mm[i + 2]) && ('>' == mm[i + 3])) // ' -->'
728 i += 4;
729 }
730 frg0 = newFragment(FT_TEXT, &mm[k], m - k);
731 ret->fragments->addlast(ret->fragments, frg0, sizeof(*frg0));
732 ret->fragments->addlast(ret->fragments, frg1, sizeof(*frg1));
733 k = i;
734 break;
735 }
736 }
737 }
738 }
739 }
740 }
741 }
742 }
743 }
744 frg0 = newFragment(FT_TEXT, &mm[k], length - k);
745 ret->fragments->addlast(ret->fragments, frg0, sizeof(*frg0));
746
747 if (-1 == munmap(mm, length))
748 FCGI_fprintf(FCGI_stderr, "Failed to munmap %s\n", file);
749
750 HTMLfileCache->put(HTMLfileCache, file, ret, sizeof(*ret));
751 }
752 }
753 close(fd);
754 }
755 }
756
757 return ret;
758}
759
760
761int main(int argc, char *argv[], char **env)
762{
763 // don't segfault if our environment is crazy
764 if (!*argv) return 127;
765
766 char *cmd = *argv;
767 char *tmp;
768 qhashtbl_t *configs = qhashtbl(0, 0);
769 lua_State *L = luaL_newstate();
770 MYSQL *database = NULL, *dbconn = NULL;
771 gridStats *stats = NULL;
772 int status, result, i;
773 void *vd;
774
775 pwd = getcwd(0, 0);
776
777 if (isatty(1))
778 {
779 I("Outputting to a terminal, not a web server.");
780 // Check if we are already running inside the proper tmux server.
781 char *eTMUX = getenv("TMUX");
782 memset(toybuf, 0, sizeof(toybuf));
783 snprintf(toybuf, sizeof(toybuf), "%s/%s", scRoot, Tsocket);
784 if (((eTMUX) && (0 == strncmp(toybuf, eTMUX, strlen(toybuf)))))
785 {
786 I("Running inside the proper tmux server.");
787 isTmux = 1;
788 }
789 else
790 I("Not running inside the proper tmux server, starting it.");
791 I("libfcgi version: %s", FCGI_VERSION);
792 I("Lua version: %s", LUA_RELEASE);
793 I("LuaJIT version: %s", LUAJIT_VERSION);
794 I("MariaDB / MySQL client version: %s", mysql_get_client_info());
795 }
796 else
797 isWeb = 1;
798
799
800/* From http://luajit.org/install.html -
801To change or extend the list of standard libraries to load, copy
802src/lib_init.c to your project and modify it accordingly. Make sure the
803jit library is loaded or the JIT compiler will not be activated.
804*/
805 luaL_openlibs(L); // Load Lua libraries.
806
807 // Load the config scripts.
808 char *cPaths[] =
809 {
810 "/etc/sledjChisl.conf.lua",
811// "/etc/sledjChisl.d/*.lua",
812 "~/.sledjChisl.conf.lua",
813// "~/.config/sledjChisl/*.lua",
814 ".sledjChisl.conf.lua",
815 NULL
816 };
817 struct stat st;
818
819
820 for (i = 0; cPaths[i]; i++)
821 {
822 memset(toybuf, 0, sizeof(toybuf));
823 if (('/' == cPaths[i][0]) || ('~' == cPaths[i][0]))
824 snprintf(toybuf, sizeof(toybuf), "%s", cPaths[i]);
825 else
826 snprintf(toybuf, sizeof(toybuf), "%s/%s", pwd, cPaths[i]);
827 if (0 != lstat(toybuf, &st))
828 continue;
829 if (!isWeb) I("Loading configuration file - %s", toybuf);
830 status = luaL_loadfile(L, toybuf);
831 if (status) // If something went wrong, error message is at the top of the stack.
832 E("Couldn't load file: %s", lua_tostring(L, -1));
833 else
834 {
835 result = lua_pcall(L, 0, LUA_MULTRET, 0);
836 if (result)
837 E("Failed to run script: %s", lua_tostring(L, -1));
838 else
839 {
840 lua_getglobal(L, "config");
841 lua_pushnil(L);
842 while(lua_next(L, -2) != 0)
843 {
844 char *n = (char *) lua_tostring(L, -2);
845
846 // Numbers can convert to strings, so check for numbers before checking for strings.
847 // On the other hand, strings that can be converted to numbers also pass lua_isnumber(). sigh
848 if (lua_isnumber(L, -1))
849 {
850 float v = lua_tonumber(L, -1);
851 configs->put(configs, n, &v, sizeof(float));
852 }
853 else if (lua_isstring(L, -1))
854 configs->putstr(configs, n, (char *) lua_tostring(L, -1));
855 else
856 {
857 char *v = (char *) lua_tostring(L, -1);
858 E("Unknown config variable type for %s = %s", n, v);
859 }
860 lua_pop(L, 1);
861 }
862 }
863 }
864 }
865 if ((vd = configs->get (configs, "loadAverageInc", NULL, false)) != NULL) {loadAverageInc = *((float *) vd); D("Setting loadAverageInc = %f", loadAverageInc);}
866 if ((vd = configs->get (configs, "simTimeOut", NULL, false)) != NULL) {simTimeOut = (int) *((float *) vd); D("Setting simTimeOut = %d", simTimeOut);}
867 if ((tmp = configs->getstr(configs, "scRoot", false)) != NULL) {scRoot = tmp; D("Setting scRoot = %s", scRoot);}
868 if ((tmp = configs->getstr(configs, "scUser", false)) != NULL) {scUser = tmp; D("Setting scUser = %s", scUser);}
869 if ((tmp = configs->getstr(configs, "Tconsole", false)) != NULL) {Tconsole = tmp; D("Setting Tconsole = %s", Tconsole);}
870 if ((tmp = configs->getstr(configs, "Tsocket", false)) != NULL) {Tsocket = tmp; D("Setting Tsocket = %s", Tsocket);}
871 if ((tmp = configs->getstr(configs, "Ttab", false)) != NULL) {Ttab = tmp; D("Setting Ttab = %s", Ttab);}
872 if ((tmp = configs->getstr(configs, "webRoot", false)) != NULL) {webRoot = tmp; D("Setting webRoot = %s", webRoot);}
873
874
875 if (isTmux || isWeb)
876 {
877 char *d;
878 memset(toybuf, 0, sizeof(toybuf));
879// TODO - the problem here is that web server isn't in the opensimmc group, so can't read this file ,with the database credentials.
880// Other web server things have access to database credentials, so not like this is a big problem.
881// For now I've just opened up the perms on it on my desktop.
882 snprintf(toybuf, sizeof(toybuf), "%s/config/config.ini", scRoot);
883
884 qlisttbl_t *qconfig = qconfig_parse_file(NULL, toybuf, '=');
885 d = qstrunchar(qconfig->getstr(qconfig, "Const.ConnectionString", false), '"', '"');
886
887 if (NULL == d)
888 {
889 E("No database credentials in %s!", toybuf);
890 goto finished;
891 }
892 else
893 {
894 char *p0, *p1, *p2;
895 if (NULL == (d = strdup(d)))
896 {
897 E("Out of memory!");
898 goto finished;
899 }
900 // Data Source=MYSQL_HOST;Database=MYSQL_DB;User ID=MYSQL_USER;Password=MYSQL_PASSWORD;Old Guids=true;
901 p0 = d;
902 while (NULL != p0)
903 {
904 p1 = strchr(p0, '=');
905 if (NULL == p1) break;
906 *p1 = '\0';
907 p2 = strchr(p1 + 1, ';');
908 if (NULL == p2) break;
909 *p2 = '\0';
910 configs->putstr(configs, p0, p1 + 1); // NOTE - this allocs memory for it's key and it's data.
911 p0 = p2 + 1;
912 if ('\0' == *p0)
913 p0 = NULL;
914 };
915 free(d);
916 }
917 if (mysql_library_init(argc, argv, NULL))
918 {
919 E("mysql_library_init() failed!");
920 goto finished;
921 }
922
923 database = mysql_init(NULL);
924 if (NULL == database)
925 {
926 E("mysql_init() failed - %s", mysql_error(database));
927 goto finished;
928 }
929 else
930 {
931 // I have no idea what evil magic toybox is doing, but this ALWAYS CRASHES, no matter what I do.
932 dbconn = mysql_real_connect(database,
933 configs->getstr(configs, "Data Source", true),
934 configs->getstr(configs, "User ID", true),
935 configs->getstr(configs, "Password", true),
936 configs->getstr(configs, "Database", true),
937// 3036, "/var/run/mysqld/mysqld.sock",
938 0, NULL,
939 CLIENT_FOUND_ROWS | CLIENT_LOCAL_FILES | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS);
940 if (NULL == dbconn)
941 {
942 E("mysql_real_connect() failed - %s", mysql_error(database));
943 goto finished;
944 }
945
946 // Need to kick this off.
947 stats = getStats(database, stats);
948 char *h = qstrunchar(qconfig->getstr(qconfig, "Const.HostName", false), '"', '"');
949 char *p = qstrunchar(qconfig->getstr(qconfig, "Const.PublicPort", false), '"', '"');
950 stats->stats->putstr(stats->stats, "grid", qstrunchar(qconfig->getstr(qconfig, "Const.GridName", false), '"', '"'));
951 stats->stats->putstr(stats->stats, "HostName", h);
952 stats->stats->putstr(stats->stats, "PublicPort", p);
953 snprintf(toybuf, sizeof(toybuf), "http://%s:%s/", h, p);
954
955 stats->stats->putstr(stats->stats, "uri", toybuf);
956 }
957 }
958
959
960 if (isWeb)
961 {
962 char **initialEnv = env;
963 int count = 0, entries, bytes;
964
965 // FCGI_LISTENSOCK_FILENO is the socket to the web server.
966 // STDOUT and STDERR go to the web servers error log, or at least it does in Apache 2.
967// fprintf(stdout, "STDOUT Started SledjChisl web server.\n");
968// fprintf(stderr, "STDERR Started SledjChisl web server.\n");
969 I("Running SledjChisl inside a web server.");
970
971 if (1 == argc)
972 D("no args");
973 else
974 {
975 for (i = 0; argv[i] != NULL; i++)
976 D("ARG %s", argv[i]);
977 }
978 printEnv(env);
979
980 struct stat statbuf;
981 if (-1 == fstat(FCGI_LISTENSOCK_FILENO, &statbuf))
982 tb_error_msg("fstat() failed");
983 else
984 {
985 if (S_ISREG (statbuf.st_mode)) D("regular file");
986 else if (S_ISDIR (statbuf.st_mode)) D("directory");
987 else if (S_ISCHR (statbuf.st_mode)) D("character device");
988 else if (S_ISBLK (statbuf.st_mode)) D("block device");
989 else if (S_ISFIFO(statbuf.st_mode)) D("FIFO (named pipe)");
990 else if (S_ISLNK (statbuf.st_mode)) D("symbolic link");
991 else if (S_ISSOCK(statbuf.st_mode)) D("socket");
992 else D("unknown file descriptor type");
993 }
994
995
996 while (FCGI_Accept() != -1)
997 {
998 if (NULL == getenv("PATH_INFO")) {msleep(1000); continue;}
999
1000 char *contentLength = getenv("CONTENT_LENGTH");
1001 int len;
1002
1003 if (contentLength != NULL)
1004 len = strtol(contentLength, NULL, 10);
1005 else
1006 len = 0;
1007
1008
1009 if (FCGX_IsCGI())
1010 D("Started SledjChisl CGI web request ROLE = %s!", getenv("FCGI_ROLE"));
1011 else
1012 D("Started SledjChisl FCGI web request ROLE = %s.", getenv("FCGI_ROLE"));
1013
1014 memset(toybuf, 0, sizeof(toybuf));
1015 snprintf(toybuf, sizeof(toybuf), "%s/html%s", webRoot, getenv("PATH_INFO"));
1016
1017 HTMLfile *thisFile = checkHTMLcache(toybuf);
1018
1019 getStats(database, stats);
1020
1021// This is dynamic content, it's always gonna be modified. I think.
1022// char *since = getenv("If-Modified-Since");
1023// if (NULL != since)
1024// {
1025// time_t snc = qtime_parse_gmtstr(since);
1026// if (thisFile->last.tv_sec < snc)
1027// {
1028// D("Status: 304 Not Modified - %s", toybuf);
1029// FCGI_printf("Status: 304 Not Modified\r\n");
1030// goto fcgiDone;
1031// }
1032// }
1033
1034 FCGI_printf("Status: 200 OK\r\n"
1035 "Content-type: text/html\r\n"
1036 "\r\n");
1037 if (len <= 0)
1038 D("No data from standard input.");
1039 else
1040 {
1041 int i, ch;
1042
1043 FCGI_printf("Standard input:<br>\n<pre>\n");
1044 for (i = 0; i < len; i++)
1045 {
1046 if ((ch = FCGI_getchar()) < 0)
1047 {
1048 E("Error: Not enough bytes received on standard input<p>");
1049 break;
1050 }
1051 FCGI_putchar(ch);
1052 }
1053 FCGI_printf("\n</pre><p>\n");
1054 }
1055
1056
1057 qlist_obj_t obj;
1058 memset((void *) &obj, 0, sizeof(obj)); // must be cleared before call
1059 thisFile->fragments->lock(thisFile->fragments);
1060 while (thisFile->fragments->getnext(thisFile->fragments, &obj, false) == true)
1061 {
1062 fragment *frg = (fragment *) obj.data;
1063 if (NULL == frg->text)
1064 {
1065 E("NULL fragment!");
1066 continue;
1067 }
1068 switch (frg->type)
1069 {
1070 case FT_TEXT:
1071 {
1072 // Silly thing triggers a "mod_fcgid: ap_pass_brigade failed in handle_request_ipc function" in Apache 2.4, sometimes.
1073 // Haven't seen one for some time.
1074 // https://www.tablix.org/~avian/blog/archives/2016/05/on_ap_pass_brigade_failed/
1075 int l = FCGI_fwrite(frg->text, 1, frg->length, FCGI_stdout);
1076 if (l != frg->length)
1077 E("Failed to write %d != %d", l, frg->length);
1078 break;
1079 }
1080
1081 case FT_PARAM:
1082 {
1083 if (strcmp("DEBUG", frg->text) == 0)
1084 {
1085 FCGI_printf("<h1>FastCGI SledjChisl</h1>\n"
1086 "Request number %d, Process ID: %d<p>\n", ++count, getpid());
1087 FCGI_printf("<p>libfcgi version: %s</p>\n", FCGI_VERSION);
1088 FCGI_printf("<p>Lua version: %s</p>\n", LUA_RELEASE);
1089 FCGI_printf("<p>LuaJIT version: %s</p>\n", LUAJIT_VERSION);
1090 FCGI_printf("<p>MySQL client version: %s</p>\n", mysql_get_client_info());
1091 PrintEnv("Initial environment", initialEnv);
1092 PrintEnv("Request environment", environ);
1093 }
1094 else if (strcmp("URL", frg->text) == 0)
1095 FCGI_printf("%s://%s%s", getenv("REQUEST_SCHEME"), getenv("HTTP_HOST"), getenv("SCRIPT_NAME"));
1096 else
1097 {
1098 if ((tmp = stats->stats->getstr(stats->stats, frg->text, false)) != NULL)
1099 FCGI_printf("%s", tmp);
1100 else
1101 FCGI_printf("<b>%s</b>", frg->text);
1102 }
1103 break;
1104 }
1105
1106 case FT_LUA:
1107 break;
1108 }
1109 }
1110 thisFile->fragments->unlock(thisFile->fragments);
1111
1112fcgiDone:
1113 D("Finishing SledjChisl web request.");
1114 FCGI_Finish();
1115 }
1116
1117 FCGI_fprintf(FCGI_stdout, "Stopped SledjChisl web server.\n");
1118 D("Stopped SledjChisl web server.");
1119
1120 goto finished;
1121 }
1122
1123
1124 if (!isTmux)
1125 { // Let's see if the proper tmux server is even running.
1126 memset(toybuf, 0, sizeof(toybuf));
1127 snprintf(toybuf, sizeof(toybuf), "%s %s/%s -q list-sessions 2>/dev/null | grep -q %s:", Tcmd, scRoot, Tsocket, Tconsole);
1128 i = system(toybuf);
1129 if (WIFEXITED(i))
1130 {
1131 if (0 != WEXITSTATUS(i)) // No such sesion, create it.
1132 {
1133 memset(toybuf, 0, sizeof(toybuf));
1134 // The sudo is only so that the session is owned by opensim, otherwise it's owned by whoever ran this script, which is a likely security hole.
1135 // After the session is created, we rely on the caches directory to be group sticky, so that anyone in the opensim group can attach to the tmux socket.
1136 snprintf(toybuf, sizeof(toybuf),
1137 "sudo -Hu %s %s %s/%s new-session -d -s %s -n '%s' \\; split-window -bhp 50 -t '%s:' bash -c './sledjchisl; cd %s; bash'",
1138 scUser, Tcmd, scRoot, Tsocket, Tconsole, Ttab, Tconsole, scRoot);
1139 i = system(toybuf);
1140 if (!WIFEXITED(i))
1141 E("tmux new-session command failed!");
1142 }
1143 // Join the session.
1144 memset(toybuf, 0, sizeof(toybuf));
1145 snprintf(toybuf, sizeof(toybuf), "%s %s/%s select-window -t '%s' \\; attach-session -t '%s'", Tcmd, scRoot, Tsocket, Tconsole, Tconsole);
1146 i = system(toybuf);
1147 if (!WIFEXITED(i))
1148 E("tmux attach-session command failed!");
1149 goto finished;
1150 }
1151 else
1152 E("tmux list-sessions command failed!");
1153 }
1154
1155
1156
1157 simList *sims = getSims();
1158 if (1)
1159 {
1160 struct sysinfo info;
1161 float la;
1162
1163 sysinfo(&info);
1164 la = info.loads[0]/65536.0;
1165
1166 if (!checkSimIsRunning("ROBUST"))
1167 {
1168 char *d = xmprintf("%s.{right}", Ttab);
1169 char *c = xmprintf("cd %s/current/bin", scRoot);
1170
1171 I("ROBUST is starting up.");
1172 sendTmuxCmd(d, c);
1173 free(c);
1174 c = xmprintf("mono Robust.exe -inidirectory=%s/config/ROBUST", scRoot);
1175 sendTmuxCmd(d, c);
1176 free(c);
1177 waitTmuxText(d, "INITIALIZATION COMPLETE FOR ROBUST");
1178 I("ROBUST is done starting up.");
1179 la = waitLoadAverage(la, loadAverageInc / 3.0, simTimeOut / 3);
1180 free(d);
1181 }
1182
1183 for (i = 0; i < sims->num; i++)
1184 {
1185 char *sim = sims->sims[i], *name = getSimName(sims->sims[i]);
1186
1187 if (!checkSimIsRunning(sim))
1188 {
1189 I("%s is starting up.", name);
1190 memset(toybuf, 0, sizeof(toybuf));
1191 snprintf(toybuf, sizeof(toybuf), "%s %s/%s new-window -dn '%s' -t '%s:%d' 'cd %s/current/bin; mono OpenSim.exe -inidirectory=%s/config/%s'",
1192 Tcmd, scRoot, Tsocket, name, Tconsole, i + 1, scRoot, scRoot, sim);
1193 int r = system(toybuf);
1194 if (!WIFEXITED(r))
1195 E("tmux new-window command failed!");
1196 else
1197 {
1198 memset(toybuf, 0, sizeof(toybuf));
1199 snprintf(toybuf, sizeof(toybuf), "INITIALIZATION COMPLETE FOR %s", name);
1200 waitTmuxText(name, toybuf);
1201 I("%s is done starting up.", name);
1202 la = waitLoadAverage(la, loadAverageInc, simTimeOut);
1203 }
1204 }
1205 }
1206
1207 }
1208 else if (!strcmp(cmd, "create")) // "create name x,y size"
1209 {
1210 }
1211 else if (!strcmp(cmd, "start")) // "start sim01" "start Welcome" "start" start everything
1212 {
1213 }
1214 else if (!strcmp(cmd, "backup")) // "backup onefang rejected" "backup sim01" "backup Welcome" "backup" backup everything
1215 { // If it's not a sim code, and not a sim name, it's an account inventory.
1216 }
1217 else if (!strcmp(cmd, "gitAR")) // "gitAR i name"
1218 {
1219 }
1220 else if (!strcmp(cmd, "stop")) // "stop sim01" "stop Welcome" "stop" stop everything
1221 {
1222 }
1223
1224
1225 double sum;
1226
1227 // Load the file containing the script we are going to run
1228 status = luaL_loadfile(L, "script.lua");
1229 if (status)
1230 {
1231 // If something went wrong, error message is at the top of the stack
1232 E("Couldn't load file: %s", lua_tostring(L, -1));
1233 goto finished;
1234 }
1235
1236 /*
1237 * Ok, now here we go: We pass data to the lua script on the stack.
1238 * That is, we first have to prepare Lua's virtual stack the way we
1239 * want the script to receive it, then ask Lua to run it.
1240 */
1241 lua_newtable(L); /* We will pass a table */
1242
1243 /*
1244 * To put values into the table, we first push the index, then the
1245 * value, and then call lua_rawset() with the index of the table in the
1246 * stack. Let's see why it's -3: In Lua, the value -1 always refers to
1247 * the top of the stack. When you create the table with lua_newtable(),
1248 * the table gets pushed into the top of the stack. When you push the
1249 * index and then the cell value, the stack looks like:
1250 *
1251 * <- [stack bottom] -- table, index, value [top]
1252 *
1253 * So the -1 will refer to the cell value, thus -3 is used to refer to
1254 * the table itself. Note that lua_rawset() pops the two last elements
1255 * of the stack, so that after it has been called, the table is at the
1256 * top of the stack.
1257 */
1258 for (i = 1; i <= 5; i++)
1259 {
1260 lua_pushnumber(L, i); // Push the table index
1261 lua_pushnumber(L, i*2); // Push the cell value
1262 lua_rawset(L, -3); // Stores the pair in the table
1263 }
1264
1265 // By what name is the script going to reference our table?
1266 lua_setglobal(L, "foo");
1267
1268 // Ask Lua to run our little script
1269 result = lua_pcall(L, 0, LUA_MULTRET, 0);
1270 if (result)
1271 {
1272 E("Failed to run script: %s", lua_tostring(L, -1));
1273 goto finished;
1274 }
1275
1276 // Get the returned value at the top of the stack (index -1)
1277 sum = lua_tonumber(L, -1);
1278
1279 I("Script returned: %.0f", sum);
1280
1281 lua_pop(L, 1); // Take the returned value out of the stack
1282
1283 puts("");
1284 fflush(stdout);
1285
1286finished:
1287 if (database) mysql_close(database);
1288 mysql_library_end();
1289 lua_close(L);
1290 if (stats)
1291 {
1292 if (stats->stats) stats->stats->free(stats->stats);
1293 free(stats);
1294 }
1295 if (configs) configs->free(configs);
1296 return EXIT_SUCCESS;
1297}
diff --git a/src/toybox.c b/src/toybox.c
new file mode 100644
index 0000000..cd1071b
--- /dev/null
+++ b/src/toybox.c
@@ -0,0 +1,520 @@
1#include "toybox.h"
2
3
4char libbuf[4096];
5
6
7// From lib.c
8
9void tb_verror_msg(char *msg, int err, va_list va)
10{
11 char *s = ": %s";
12
13// fprintf(stderr, "%s: ", toys.which->name);
14 if (msg) vfprintf(stderr, msg, va);
15 else s+=2;
16 if (err>0) fprintf(stderr, s, strerror(err));
17// if (err<0 && CFG_TOYBOX_HELP)
18// fprintf(stderr, " (see \"%s --help\")", toys.which->name);
19 if (msg || err) putc('\n', stderr);
20// if (!toys.exitval) toys.exitval++;
21}
22
23// These functions don't collapse together because of the va_stuff.
24
25void tb_error_msg(char *msg, ...)
26{
27 va_list va;
28
29 va_start(va, msg);
30 tb_verror_msg(msg, 0, va);
31 va_end(va);
32}
33
34void tb_perror_msg(char *msg, ...)
35{
36 va_list va;
37
38 va_start(va, msg);
39 tb_verror_msg(msg, errno, va);
40 va_end(va);
41}
42
43// Die with an error message.
44void tb_error_exit(char *msg, ...)
45{
46 va_list va;
47
48 va_start(va, msg);
49 tb_verror_msg(msg, 0, va);
50 va_end(va);
51
52 xexit();
53}
54
55// Die with an error message and strerror(errno)
56void tb_perror_exit(char *msg, ...)
57{
58 // Die silently if our pipeline exited.
59 if (errno != EPIPE) {
60 va_list va;
61
62 va_start(va, msg);
63 tb_verror_msg(msg, errno, va);
64 va_end(va);
65 }
66
67 xexit();
68}
69
70// If you want to explicitly disable the printf() behavior (because you're
71// printing user-supplied data, or because android's static checker produces
72// false positives for 'char *s = x ? "blah1" : "blah2"; printf(s);' and it's
73// -Werror there for policy reasons).
74void tb_error_msg_raw(char *msg)
75{
76 tb_error_msg("%s", msg);
77}
78
79void tb_perror_msg_raw(char *msg)
80{
81 tb_perror_msg("%s", msg);
82}
83
84void tb_error_exit_raw(char *msg)
85{
86 tb_error_exit("%s", msg);
87}
88
89void tb_perror_exit_raw(char *msg)
90{
91 tb_perror_exit("%s", msg);
92}
93
94// Sleep for this many thousandths of a second
95void msleep(long milliseconds)
96{
97 struct timespec ts;
98
99 ts.tv_sec = milliseconds/1000;
100 ts.tv_nsec = (milliseconds%1000)*1000000;
101 nanosleep(&ts, &ts);
102}
103
104// Slow, but small.
105char *get_line(int fd)
106{
107 char c, *buf = NULL;
108 long len = 0;
109
110 for (;;) {
111 if (1>read(fd, &c, 1)) break;
112 if (!(len & 63)) buf=xrealloc(buf, len+65);
113 if ((buf[len++]=c) == '\n') break;
114 }
115 if (buf) {
116 buf[len]=0;
117 if (buf[--len]=='\n') buf[len]=0;
118 }
119
120 return buf;
121}
122
123// The qsort man page says you can use alphasort, the posix committee
124// disagreed, and doubled down: http://austingroupbugs.net/view.php?id=142
125// So just do our own. (The const is entirely to humor the stupid compiler.)
126int qstrcmp(const void *a, const void *b)
127{
128 return strcmp(*(char **)a, *(char **)b);
129}
130
131
132
133// From xwrap.c
134
135// We replaced exit(), _exit(), and atexit() with xexit(), _xexit(), and
136// sigatexit(). This gives _xexit() the option to siglongjmp(toys.rebound, 1)
137// instead of exiting, lets xexit() report stdout flush failures to stderr
138// and change the exit code to indicate error, lets our toys.exit function
139// change happen for signal exit paths and lets us remove the functions
140// after we've called them.
141
142void _xexit(void)
143{
144// if (toys.rebound) siglongjmp(*toys.rebound, 1);
145
146// _exit(toys.exitval);
147 _exit(0);
148}
149
150void xexit(void)
151{
152 // Call toys.xexit functions in reverse order added.
153// while (toys.xexit) {
154// struct arg_list *al = tb_llist_pop(&toys.xexit);
155
156 // typecast xexit->arg to a function pointer, then call it using invalid
157 // signal 0 to let signal handlers tell actual signal from regular exit.
158// ((void (*)(int))(al->arg))(0);
159
160// free(al);
161// }
162 xflush(1);
163 _xexit();
164}
165
166// Die unless we can allocate memory.
167void *xmalloc(size_t size)
168{
169 void *ret = malloc(size);
170 if (!ret) tb_error_exit("xmalloc(%ld)", (long)size);
171
172 return ret;
173}
174
175// Die unless we can allocate prezeroed memory.
176void *xzalloc(size_t size)
177{
178 void *ret = xmalloc(size);
179 memset(ret, 0, size);
180 return ret;
181}
182
183// Die unless we can change the size of an existing allocation, possibly
184// moving it. (Notice different arguments from libc function.)
185void *xrealloc(void *ptr, size_t size)
186{
187 ptr = realloc(ptr, size);
188 if (!ptr) tb_error_exit("xrealloc");
189
190 return ptr;
191}
192
193// Die unless we can allocate a copy of this many bytes of string.
194char *xstrndup(char *s, size_t n)
195{
196 char *ret = strndup(s, n);
197
198 if (!ret) tb_error_exit("xstrndup");
199
200 return ret;
201}
202
203// Die unless we can allocate a copy of this string.
204char *xstrdup(char *s)
205{
206 return xstrndup(s, strlen(s));
207}
208
209void *xmemdup(void *s, long len)
210{
211 void *ret = xmalloc(len);
212 memcpy(ret, s, len);
213
214 return ret;
215}
216
217// Die unless we can allocate enough space to sprintf() into.
218char *xmprintf(char *format, ...)
219{
220 va_list va, va2;
221 int len;
222 char *ret;
223
224 va_start(va, format);
225 va_copy(va2, va);
226
227 // How long is it?
228 len = vsnprintf(0, 0, format, va);
229 len++;
230 va_end(va);
231
232 // Allocate and do the sprintf()
233 ret = xmalloc(len);
234 vsnprintf(ret, len, format, va2);
235 va_end(va2);
236
237 return ret;
238}
239
240// if !flush just check for error on stdout without flushing
241void xflush(int flush)
242{
243 if ((flush && fflush(0)) || ferror(stdout))
244;// if (!toys.exitval) tb_perror_msg("write");
245}
246
247// Die unless we can open/create a file, returning file descriptor.
248// The meaning of O_CLOEXEC is reversed (it defaults on, pass it to disable)
249// and WARN_ONLY tells us not to exit.
250int xcreate_stdio(char *path, int flags, int mode)
251{
252 int fd = open(path, (flags^O_CLOEXEC)&~WARN_ONLY, mode);
253
254 if (fd == -1) ((mode&WARN_ONLY) ? tb_perror_msg_raw : tb_perror_exit_raw)(path);
255 return fd;
256}
257
258// Die unless we can open a file, returning file descriptor.
259int xopen_stdio(char *path, int flags)
260{
261 return xcreate_stdio(path, flags, 0);
262}
263
264void xclose(int fd)
265{
266 if (close(fd)) tb_perror_exit("xclose");
267}
268
269int xdup(int fd)
270{
271 if (fd != -1) {
272 fd = dup(fd);
273 if (fd == -1) tb_perror_exit("xdup");
274 }
275 return fd;
276}
277
278// Move file descriptor above stdin/stdout/stderr, using /dev/null to consume
279// old one. (We should never be called with stdin/stdout/stderr closed, but...)
280int notstdio(int fd)
281{
282 if (fd<0) return fd;
283
284 while (fd<3) {
285 int fd2 = xdup(fd);
286
287 close(fd);
288 xopen_stdio("/dev/null", O_RDWR);
289 fd = fd2;
290 }
291
292 return fd;
293}
294
295// Open a file descriptor NOT in stdin/stdout/stderr
296int xopen(char *path, int flags)
297{
298 return notstdio(xopen_stdio(path, flags));
299}
300
301// Open read only, treating "-" as a synonym for stdin, defaulting to warn only
302int openro(char *path, int flags)
303{
304 if (!strcmp(path, "-")) return 0;
305
306 return xopen(path, flags^WARN_ONLY);
307}
308
309// Open read only, treating "-" as a synonym for stdin.
310int xopenro(char *path)
311{
312 return openro(path, O_RDONLY|WARN_ONLY);
313}
314
315// Compile a regular expression into a regex_t
316void xregcomp(regex_t *preg, char *regex, int cflags)
317{
318 int rc;
319
320 // BSD regex implementations don't support the empty regex (which isn't
321 // allowed in the POSIX grammar), but glibc does. Fake it for BSD.
322 if (!*regex) {
323 regex = "()";
324 cflags |= REG_EXTENDED;
325 }
326
327 if ((rc = regcomp(preg, regex, cflags))) {
328 regerror(rc, preg, libbuf, sizeof(libbuf));
329 tb_error_exit("bad regex: %s", libbuf);
330 }
331}
332
333
334
335// From dirtree.c
336
337int tb_isdotdot(char *name)
338{
339 if (name[0]=='.' && (!name[1] || (name[1]=='.' && !name[2]))) return 1;
340
341 return 0;
342}
343
344// Create a tb_dirtree node from a path, with stat and symlink info.
345// (This doesn't open directory filehandles yet so as not to exhaust the
346// filehandle space on large trees, tb_dirtree_handle_callback() does that.)
347
348struct tb_dirtree *tb_dirtree_add_node(struct tb_dirtree *parent, char *name, int flags)
349{
350 struct tb_dirtree *dt = 0;
351 struct stat st;
352 int len = 0, linklen = 0, statless = 0;
353
354 if (name) {
355 // open code fd = because haven't got node to call tb_dirtree_parentfd() on yet
356 int fd = parent ? parent->dirfd : AT_FDCWD,
357 sym = AT_SYMLINK_NOFOLLOW*!(flags&TB_DIRTREE_SYMFOLLOW);
358
359 // stat dangling symlinks
360 if (fstatat(fd, name, &st, sym)) {
361 // If we got ENOENT without NOFOLLOW, try again with NOFOLLOW.
362 if (errno!=ENOENT || sym || fstatat(fd, name, &st, AT_SYMLINK_NOFOLLOW)) {
363 if (flags&TB_DIRTREE_STATLESS) statless++;
364 else goto error;
365 }
366 }
367 if (!statless && S_ISLNK(st.st_mode)) {
368 if (0>(linklen = readlinkat(fd, name, libbuf, 4095))) goto error;
369 libbuf[linklen++]=0;
370 }
371 len = strlen(name);
372 }
373
374 // Allocate/populate return structure
375 dt = xmalloc((len = sizeof(struct tb_dirtree)+len+1)+linklen);
376 memset(dt, 0, statless ? offsetof(struct tb_dirtree, again)
377 : offsetof(struct tb_dirtree, st));
378 dt->parent = parent;
379 dt->again = statless ? 2 : 0;
380 if (!statless) memcpy(&dt->st, &st, sizeof(struct stat));
381 strcpy(dt->name, name ? name : "");
382 if (linklen) dt->symlink = memcpy(len+(char *)dt, libbuf, linklen);
383
384 return dt;
385
386error:
387 if (!(flags&TB_DIRTREE_SHUTUP) && !tb_isdotdot(name)) {
388 char *path = parent ? tb_dirtree_path(parent, 0) : "";
389
390 tb_perror_msg("%s%s%s", path, parent ? "/" : "", name);
391 if (parent) free(path);
392 }
393 if (parent) parent->symlink = (char *)1;
394 free(dt);
395 return 0;
396}
397
398// Return path to this node, assembled recursively.
399
400// Initial call can pass in NULL to plen, or point to an int initialized to 0
401// to return the length of the path, or a value greater than 0 to allocate
402// extra space if you want to append your own text to the string.
403
404char *tb_dirtree_path(struct tb_dirtree *node, int *plen)
405{
406 char *path;
407 int len;
408
409 if (!node) {
410 path = xmalloc(*plen);
411 *plen = 0;
412 return path;
413 }
414
415 len = (plen ? *plen : 0)+strlen(node->name)+1;
416 path = tb_dirtree_path(node->parent, &len);
417 if (len && path[len-1] != '/') path[len++]='/';
418 len = stpcpy(path+len, node->name) - path;
419 if (plen) *plen = len;
420
421 return path;
422}
423
424int tb_dirtree_parentfd(struct tb_dirtree *node)
425{
426 return node->parent ? node->parent->dirfd : AT_FDCWD;
427}
428
429// Handle callback for a node in the tree. Returns saved node(s) if
430// callback returns TB_DIRTREE_SAVE, otherwise frees consumed nodes and
431// returns NULL. If !callback return top node unchanged.
432// If !new return TB_DIRTREE_ABORTVAL
433
434struct tb_dirtree *tb_dirtree_handle_callback(struct tb_dirtree *new,
435 int (*callback)(struct tb_dirtree *node))
436{
437 int flags;
438
439 if (!new) return TB_DIRTREE_ABORTVAL;
440 if (!callback) return new;
441 flags = callback(new);
442
443 if (S_ISDIR(new->st.st_mode) && (flags & (TB_DIRTREE_RECURSE|TB_DIRTREE_COMEAGAIN)))
444 flags = tb_dirtree_recurse(new, callback,
445 openat(tb_dirtree_parentfd(new), new->name, O_CLOEXEC), flags);
446
447 // If this had children, it was callback's job to free them already.
448 if (!(flags & TB_DIRTREE_SAVE)) {
449 free(new);
450 new = 0;
451 }
452
453 return (flags & TB_DIRTREE_ABORT)==TB_DIRTREE_ABORT ? TB_DIRTREE_ABORTVAL : new;
454}
455
456// Recursively read/process children of directory node, filtering through
457// callback(). Uses and closes supplied ->dirfd.
458
459int tb_dirtree_recurse(struct tb_dirtree *node,
460 int (*callback)(struct tb_dirtree *node), int dirfd, int flags)
461{
462 struct tb_dirtree *new, **ddt = &(node->child);
463 struct dirent *entry;
464 DIR *dir;
465
466 node->dirfd = dirfd;
467 if (node->dirfd == -1 || !(dir = fdopendir(node->dirfd))) {
468 if (!(flags & TB_DIRTREE_SHUTUP)) {
469 char *path = tb_dirtree_path(node, 0);
470 tb_perror_msg_raw(path);
471 free(path);
472 }
473 close(node->dirfd);
474
475 return flags;
476 }
477
478 // according to the fddir() man page, the filehandle in the DIR * can still
479 // be externally used by things that don't lseek() it.
480
481 // The extra parentheses are to shut the stupid compiler up.
482 while ((entry = readdir(dir))) {
483 if ((flags&TB_DIRTREE_PROC) && !isdigit(*entry->d_name)) continue;
484 if (!(new = tb_dirtree_add_node(node, entry->d_name, flags))) continue;
485 if (!new->st.st_blksize && !new->st.st_mode)
486 new->st.st_mode = entry->d_type<<12;
487 new = tb_dirtree_handle_callback(new, callback);
488 if (new == TB_DIRTREE_ABORTVAL) break;
489 if (new) {
490 *ddt = new;
491 ddt = &((*ddt)->next);
492 }
493 }
494
495 if (flags & TB_DIRTREE_COMEAGAIN) {
496 node->again |= 1;
497 flags = callback(node);
498 }
499
500 // This closes filehandle as well, so note it
501 closedir(dir);
502 node->dirfd = -1;
503
504 return flags;
505}
506
507void tb_dirtree_free(struct tb_dirtree *new)
508{
509 struct tb_dirtree *dt;
510
511 if ((dt = new)) {
512 new = 0;
513 while (dt->child) {
514 new = dt->child->next;
515 free(dt->child);
516 dt->child = new;
517 }
518 free(dt);
519 }
520}
diff --git a/src/toybox.h b/src/toybox.h
new file mode 100644
index 0000000..1f64310
--- /dev/null
+++ b/src/toybox.h
@@ -0,0 +1,278 @@
1
2#ifdef __GNUC__
3#define printf_format __attribute__((format(printf, 1, 2)))
4#else
5#define printf_format
6#endif
7
8// General posix-2008 headers
9#include <ctype.h>
10#include <dirent.h>
11#include <errno.h>
12#include <fcntl.h>
13#include <fnmatch.h>
14#include <grp.h>
15#include <inttypes.h>
16#include <limits.h>
17#include <math.h>
18#include <paths.h>
19#include <pwd.h>
20#include <regex.h>
21#include <sched.h>
22#include <setjmp.h>
23#include <signal.h>
24#include <stdarg.h>
25#include <stddef.h>
26#include <stdint.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <strings.h>
31#include <sys/mman.h>
32#include <sys/resource.h>
33#include <sys/stat.h>
34#include <sys/statvfs.h>
35#include <sys/time.h>
36#include <sys/times.h>
37#include <sys/utsname.h>
38#include <sys/wait.h>
39#include <syslog.h>
40#include <termios.h>
41#include <time.h>
42#include <unistd.h>
43#include <utime.h>
44
45// Posix networking
46
47#include <arpa/inet.h>
48#include <netdb.h>
49#include <net/if.h>
50#include <netinet/in.h>
51#include <netinet/tcp.h>
52#include <poll.h>
53#include <sys/socket.h>
54#include <sys/un.h>
55
56// Linux headers not listed by POSIX or LSB
57#include <sys/mount.h>
58#ifdef __linux__
59#include <sys/statfs.h>
60#include <sys/swap.h>
61#include <sys/sysinfo.h>
62#endif
63
64#define WARN_ONLY (1<<31)
65
66// xwrap.c
67//void xstrncpy(char *dest, char *src, size_t size);
68//void xstrncat(char *dest, char *src, size_t size);
69void _xexit(void) __attribute__((__noreturn__));
70void xexit(void) __attribute__((__noreturn__));
71//void *xmmap(void *addr, size_t length, int prot, int flags, int fd, off_t off);
72void *xmalloc(size_t size);
73void *xzalloc(size_t size);
74void *xrealloc(void *ptr, size_t size);
75char *xstrndup(char *s, size_t n);
76char *xstrdup(char *s);
77void *xmemdup(void *s, long len);
78char *xmprintf(char *format, ...) printf_format;
79//void xprintf(char *format, ...) printf_format;
80//void xputsl(char *s, int len);
81//void xputsn(char *s);
82//void xputs(char *s);
83//void xputc(char c);
84void xflush(int flush);
85//void xexec(char **argv);
86//pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(void));
87//pid_t xpopen_both(char **argv, int *pipes);
88//int xwaitpid(pid_t pid);
89//int xpclose_both(pid_t pid, int *pipes);
90//pid_t xpopen(char **argv, int *pipe, int isstdout);
91//pid_t xpclose(pid_t pid, int pipe);
92//int xrun(char **argv);
93//int xpspawn(char **argv, int*pipes);
94//void xaccess(char *path, int flags);
95//void xunlink(char *path);
96//void xrename(char *from, char *to);
97//int xtempfile(char *name, char **tempname);
98//int xcreate(char *path, int flags, int mode);
99int xopen(char *path, int flags);
100int xcreate_stdio(char *path, int flags, int mode);
101int xopen_stdio(char *path, int flags);
102int openro(char *path, int flags);
103int xopenro(char *path);
104//void xpipe(int *pp);
105void xclose(int fd);
106int xdup(int fd);
107int notstdio(int fd);
108//FILE *xfdopen(int fd, char *mode);
109//FILE *xfopen(char *path, char *mode);
110//size_t xread(int fd, void *buf, size_t len);
111//void xreadall(int fd, void *buf, size_t len);
112//void xwrite(int fd, void *buf, size_t len);
113//off_t xlseek(int fd, off_t offset, int whence);
114//char *xreadfile(char *name, char *buf, off_t len);
115//int xioctl(int fd, int request, void *data);
116//char *xgetcwd(void);
117//void xstat(char *path, struct stat *st);
118//char *xabspath(char *path, int exact);
119//void xchdir(char *path);
120//void xchroot(char *path);
121//struct passwd *xgetpwuid(uid_t uid);
122//struct group *xgetgrgid(gid_t gid);
123//struct passwd *xgetpwnam(char *name);
124//struct group *xgetgrnam(char *name);
125//unsigned xgetuid(char *name);
126//unsigned xgetgid(char *name);
127//void xsetuser(struct passwd *pwd);
128//char *xreadlink(char *name);
129//double xstrtod(char *s);
130//long xparsetime(char *arg, long units, long *fraction);
131//long long xparsemillitime(char *arg);
132//void xpidfile(char *name);
133void xregcomp(regex_t *preg, char *rexec, int cflags);
134//char *xtzset(char *new);
135//void xsignal_flags(int signal, void *handler, int flags);
136//void xsignal(int signal, void *handler);
137//time_t xvali_date(struct tm *tm, char *str);
138//void xparsedate(char *str, time_t *t, unsigned *nano, int endian);
139//char *xgetline(FILE *fp, int *len);
140
141// lib.c
142void tb_verror_msg(char *msg, int err, va_list va);
143void tb_error_msg(char *msg, ...) printf_format;
144void tb_perror_msg(char *msg, ...) printf_format;
145void tb_error_exit(char *msg, ...) printf_format __attribute__((__noreturn__));
146void tb_perror_exit(char *msg, ...) printf_format __attribute__((__noreturn__));
147//void help_exit(char *msg, ...) printf_format __attribute__((__noreturn__));
148void tb_error_msg_raw(char *msg);
149void tb_perror_msg_raw(char *msg);
150void tb_error_exit_raw(char *msg);
151void tb_perror_exit_raw(char *msg);
152//ssize_t readall(int fd, void *buf, size_t len);
153//ssize_t writeall(int fd, void *buf, size_t len);
154//off_t lskip(int fd, off_t offset);
155//#define MKPATHAT_MKLAST 1
156//#define MKPATHAT_MAKE 2
157//#define MKPATHAT_VERBOSE 4
158//int mkpathat(int atfd, char *dir, mode_t lastmode, int flags);
159//int mkpath(char *dir);
160//struct string_list **splitpath(char *path, struct string_list **list);
161//char *readfileat(int dirfd, char *name, char *buf, off_t *len);
162//char *readfile(char *name, char *buf, off_t len);
163void msleep(long milliseconds);
164//void nanomove(struct timespec *ts, long long offset);
165//long long nanodiff(struct timespec *old, struct timespec *new);
166//int highest_bit(unsigned long l);
167//int64_t peek_le(void *ptr, unsigned size);
168//int64_t peek_be(void *ptr, unsigned size);
169//int64_t peek(void *ptr, unsigned size);
170//void poke_le(void *ptr, long long val, unsigned size);
171//void poke_be(void *ptr, long long val, unsigned size);
172//void poke(void *ptr, long long val, unsigned size);
173//struct string_list *find_in_path(char *path, char *filename);
174//long long estrtol(char *str, char **end, int base);
175//long long xstrtol(char *str, char **end, int base);
176//long long atolx(char *c);
177//long long atolx_range(char *numstr, long long low, long long high);
178//int stridx(char *haystack, char needle);
179//int utf8towc(wchar_t *wc, char *str, unsigned len);
180//char *strlower(char *s);
181//char *strafter(char *haystack, char *needle);
182//char *chomp(char *s);
183//int unescape(char c);
184//char *strend(char *str, char *suffix);
185//int strstart(char **a, char *b);
186//int strcasestart(char **a, char *b);
187//off_t fdlength(int fd);
188//void loopfiles_rw(char **argv, int flags, int permissions,
189// void (*function)(int fd, char *name));
190//void loopfiles(char **argv, void (*function)(int fd, char *name));
191//void loopfiles_lines(char **argv, void (*function)(char **pline, long len));
192//long long sendfile_len(int in, int out, long long len, long long *consumed);
193//long long xsendfile_len(int in, int out, long long len);
194//void xsendfile_pad(int in, int out, long long len);
195//long long xsendfile(int in, int out);
196//int wfchmodat(int rc, char *name, mode_t mode);
197//int copy_tempfile(int fdin, char *name, char **tempname);
198//void delete_tempfile(int fdin, int fdout, char **tempname);
199//void replace_tempfile(int fdin, int fdout, char **tempname);
200//void crc_init(unsigned int *crc_table, int little_endian);
201//void base64_init(char *p);
202//int yesno(int def);
203//int fyesno(FILE *fp, int def);
204int qstrcmp(const void *a, const void *b);
205//void tb_create_uuid(char *uuid);
206//char *tb_show_uuid(char *uuid);
207//char *next_printf(char *s, char **start);
208//struct passwd *bufgetpwuid(uid_t uid);
209//struct group *bufgetgrgid(gid_t gid);
210//int readlinkat0(int dirfd, char *path, char *buf, int len);
211//int readlink0(char *path, char *buf, int len);
212//int regexec0(regex_t *preg, char *string, long len, int nmatch,
213// regmatch_t pmatch[], int eflags);
214//char *getusername(uid_t uid);
215//char *getgroupname(gid_t gid);
216//void do_lines(int fd, char delim, void (*call)(char **pline, long len));
217//long long millitime(void);
218//char *format_iso_time(char *buf, size_t len, struct timespec *ts);
219//void reset_env(struct passwd *p, int clear);
220//void loggit(int priority, char *format, ...);
221//unsigned tar_cksum(void *data);
222//int is_tar_header(void *pkt);
223//char *elf_arch_name(int type);
224
225
226// pending.c
227char *get_line(int fd);
228
229
230// dirtree.c
231
232// Values returnable from callback function (bitfield, or them together)
233// Default with no callback is 0
234
235// Add this node to the tree
236#define TB_DIRTREE_SAVE 1
237// Recurse into children
238#define TB_DIRTREE_RECURSE 2
239// Call again after handling all children of this directory
240// (Ignored for non-directories, sets linklen = -1 before second call.)
241#define TB_DIRTREE_COMEAGAIN 4
242// Follow symlinks to directories
243#define TB_DIRTREE_SYMFOLLOW 8
244// Don't warn about failure to stat
245#define TB_DIRTREE_SHUTUP 16
246// Breadth first traversal, conserves filehandles at the expense of memory
247#define TB_DIRTREE_BREADTH 32
248// skip non-numeric entries
249#define TB_DIRTREE_PROC 64
250// Return files we can't stat
251#define TB_DIRTREE_STATLESS 128
252// Don't look at any more files in this directory.
253#define TB_DIRTREE_ABORT 256
254
255#define TB_DIRTREE_ABORTVAL ((struct tb_dirtree *)1)
256
257struct tb_dirtree {
258 struct tb_dirtree *next, *parent, *child;
259 long extra; // place for user to store their stuff (can be pointer)
260 char *symlink;
261 int dirfd;
262 struct stat st;
263 char again;
264 char name[];
265};
266
267int tb_isdotdot(char *name);
268struct tb_dirtree *tb_dirtree_add_node(struct tb_dirtree *p, char *name, int flags);
269char *tb_dirtree_path(struct tb_dirtree *node, int *plen);
270int tb_dirtree_notdotdot(struct tb_dirtree *catch);
271int tb_dirtree_parentfd(struct tb_dirtree *node);
272int tb_dirtree_recurse(struct tb_dirtree *node, int (*callback)(struct tb_dirtree *node),
273 int dirfd, int symfollow);
274
275// Rob forget to do this, but at least he didn't declare it static.
276struct tb_dirtree *tb_dirtree_handle_callback(struct tb_dirtree *new, int (*callback)(struct tb_dirtree *node));
277void tb_dirtree_free(struct tb_dirtree *new);
278
diff --git a/web/SledjHamr.png b/web/SledjHamr.png
new file mode 100644
index 0000000..9089573
--- /dev/null
+++ b/web/SledjHamr.png
Binary files differ
diff --git a/web/SledjHamrIcon.png b/web/SledjHamrIcon.png
new file mode 100644
index 0000000..e3b50e1
--- /dev/null
+++ b/web/SledjHamrIcon.png
Binary files differ
diff --git a/web/SledjHamrIconSmall.png b/web/SledjHamrIconSmall.png
new file mode 100644
index 0000000..a1a6d72
--- /dev/null
+++ b/web/SledjHamrIconSmall.png
Binary files differ
diff --git a/web/about.html b/web/about.html
new file mode 100644
index 0000000..3afc962
--- /dev/null
+++ b/web/about.html
@@ -0,0 +1,11 @@
1<html>
2<title>About this grid</title>
3<head>
4<meta charset="UTF-8">
5<link rel="shortcut icon" href="SledjHamrIconSmall.png">
6</head>
7<body bgcolor="black" text="white" alink="red" link="blue" vlink="purple">
8This is a virtual world, usually called a grid, based on <a href="https://sledjhamr.org/cgit/opensim-SC/?h=SledjChisl">opensim-SC</a>.
9<p>&nbsp;</p>
10</body>
11</html>
diff --git a/web/debugStyle.css b/web/debugStyle.css
new file mode 100644
index 0000000..98645cc
--- /dev/null
+++ b/web/debugStyle.css
@@ -0,0 +1,36 @@
1.hoverWrapper0:hover #hoverShow0
2{
3 display: block;
4 border-style: solid;
5 border-color: fuchsia;
6}
7.hoverWrapper0 #hoverShow0
8{
9 display: none;
10 background-color: #222222;
11 text-align: left;
12 position: absolute;
13 width: 100%;
14 border-style: solid;
15 border-color: fuchsia;
16}
17.hoverWrapper1:hover #hoverShow1
18{
19 display: block;
20 border-style: solid;
21 border-color: fuchsia;
22}
23.hoverWrapper1 #hoverShow1
24{
25 display: none;
26 background-color: #222222;
27 text-align: left;
28 position: absolute;
29 width: 100%;
30 border-style: solid;
31 border-color: fuchsia;
32}
33.hoverItem
34{
35 border: 1px solid fuchsia;
36}
diff --git a/web/help.html b/web/help.html
new file mode 100644
index 0000000..9a75aa6
--- /dev/null
+++ b/web/help.html
@@ -0,0 +1,11 @@
1<html>
2<title>Help for this grid</title>
3<head>
4<meta charset="UTF-8">
5<link rel="shortcut icon" href="SledjHamrIconSmall.png">
6</head>
7<body bgcolor="black" text="white" alink="red" link="blue" vlink="purple">
8Include help for your grid here.
9<p>&nbsp;</p>
10</body>
11</html>
diff --git a/web/loginpage.html b/web/loginpage.html
new file mode 100644
index 0000000..bceeee5
--- /dev/null
+++ b/web/loginpage.html
@@ -0,0 +1,108 @@
1<html>
2<title><!--#echo var="grid" --> splash page</title>
3<head>
4<meta charset="UTF-8">
5<link rel="shortcut icon" href="/SledjHamrIconSmall.png">
6<meta http-equiv="refresh" content="120">
7<style>
8html, body
9{
10 margin: 0;
11 padding: 0;
12 color: white;
13}
14a {color: green;}
15a:link {color: green;}
16a:visited {color: purple;}
17a:hover {color: red;}
18a:active {color: blue;}
19
20.container
21{
22 position: relative;
23 text-align: left;
24}
25
26.top-left
27{
28 position: absolute;
29 top: 8px;
30 left: 16px;
31 width: 30%;
32}
33
34.top-right
35{
36 position: absolute;
37 top: 8px;
38 right: 16px;
39 width: 30%;
40}
41
42.centre
43{
44 position: absolute;
45 top: 50%;
46 left: 50%;
47 width: 30%;
48 transform: translate(-50%, -50%);
49}
50
51.bottom-left
52{
53 position: absolute;
54 bottom: 8px;
55 left: 16px;
56 width: 30%;
57}
58
59.bottom-right
60{
61 position: absolute;
62 bottom: 8px;
63 right: 16px;
64 width: 30%;
65}
66</style>
67<link type="text/css" rel="stylesheet" href="/debugStyle.css" media="all" />
68</head>
69<body>
70<div class="container">
71 <img src="/SledjHamr.png" alt="SledjHamr" style="width:100%;height:100%;">
72 <div class="top-left">
73 <p class="hoverItem">
74 <div class="hoverWrapper0">
75 <p>DEBUG</p>
76 <div id="hoverShow0">
77 <h1>DEBUG log</h1>
78 <p><!--#echo var="DEBUG" --></p>
79 </div>
80 </div>
81 </p>
82 <h1>Add your fancy splash page for <!--#echo var="grid" --> here.</h1>
83 <p>&nbsp;</p>
84 <p>&nbsp;</p>
85 <p>&nbsp;</p>
86 <p>Perhaps describe <!--#echo var="grid" --> here.</p>
87 </div>
88 <div class="top-right">
89 <h1>Login URI - <!--#echo var="uri" --></h1>
90 <p>&nbsp;</p>
91 <p>There are <!--#echo var="members" --> members of this grid.</p>
92 <p>There are <!--#echo var="inworld" --> locals and <!--#echo var="hgers" --> hypergrid visitors in world.</p>
93 <p>There are <!--#echo var="outworld" --> locals out on the hypergrid.</p>
94 <p>There have been <!--#echo var="day30" --> locals and visitors on this grid in the last month.</p>
95 <p>There are <!--#echo var="sims" --> regions, though some might not be online right now.</p>
96 </div>
97 <div class="centre">
98 <p>Maybe add some news or events here, or something.</p>
99 </div>
100 <div class="bottom-left">
101 <p>CSS by Taylor Temper, photo by onefang rejected.</p>
102 </div>
103 <div class="bottom-right">
104 <p><!--#echo var="grid" --> is running <a href="https://sledjhamr.org/mantisbt/project_page.php?project_id=8"><!--#echo var="version" --></a>,
105 part of the <a href="https://sledjhamr.org/git/docs/index.html">SledjHamr</a> project.</p>
106</div>
107</body>
108</html>
diff --git a/web/password_help.html b/web/password_help.html
new file mode 100644
index 0000000..aef6bd5
--- /dev/null
+++ b/web/password_help.html
@@ -0,0 +1,11 @@
1<html>
2<title>Password help for this grid</title>
3<head>
4<meta charset="UTF-8">
5<link rel="shortcut icon" href="SledjHamrIconSmall.png">
6</head>
7<body bgcolor="black" text="white" alink="red" link="blue" vlink="purple">
8Include password help for your grid here.
9<p>&nbsp;</p>
10</body>
11</html>
diff --git a/web/register.html b/web/register.html
new file mode 100644
index 0000000..46317e6
--- /dev/null
+++ b/web/register.html
@@ -0,0 +1,15 @@
1<html>
2<title>Account for this grid</title>
3<head>
4<meta charset="UTF-8">
5<link rel="shortcut icon" href="SledjHamrIconSmall.png">
6</head>
7<body bgcolor="black" text="white" alink="red" link="blue" vlink="purple">
8Include account registration help for your grid here.
9<p>&nbsp;</p>
10<p>If you want to register an account on this grid, ask the person that runs it to do that for you.</p>
11<p>&nbsp;</p>
12<p>If you want to register an account on this grid, <a href="account.html"> click here</a> (when it's written).</p>
13<p>&nbsp;</p>
14</body>
15</html>
diff --git a/web/stats.html b/web/stats.html
new file mode 100644
index 0000000..b9647c0
--- /dev/null
+++ b/web/stats.html
@@ -0,0 +1,25 @@
1<html>
2 <title><!--#echo var="grid" --> stats page</title>
3 <head>
4 <meta charset="UTF-8">
5 <link rel="shortcut icon" href="/SledjHamrIconSmall.png">
6 <meta http-equiv="refresh" content="120">
7 </head>
8 <body>
9 <h1><!--#echo var="grid" --> statistics.</h1>
10 <p>Login URI - <!--#echo var="uri" --></p>
11 <p>Login page - <a href="<!--#echo var="URL" -->/loginpage.html"><!--#echo var="URL" -->/loginpage.html</a></p>
12 <p>There are <!--#echo var="members" --> members of this grid.</p>
13 <p>There are <!--#echo var="inworld" --> locals and <!--#echo var="hgers" --> hypergrid visitors in world right now.</p>
14 <p>There are <!--#echo var="outworld" --> locals out on the hypergrid right now.</p>
15 <p>There have been <!--#echo var="locDay30" --> locals and <!--#echo var="HGday30" --> hypergrid visitors on this grid in the last 30 days (<!--#echo var="day30" --> total).</p>
16 <p>There have been <!--#echo var="locDay60" --> locals and <!--#echo var="HGday60" --> hypergrid visitors on this grid in the last 60 days (<!--#echo var="day60" --> total).</p>
17 <p>There are <!--#echo var="sims" --> regions, though <!--#echo var="offlineSims" --> might not be online right now.</p>
18 <p>There may be <!--#echo var="onlineSims" --> regions online, with a total area of roughly <!--#echo var="simsSize" --> square metres.</p>
19 <p>There are <!--#echo var="varRegions" --> varregions that might be online now.</p>
20 <p>There are <!--#echo var="singleSims" --> normal regions that might be online now.</p>
21 <p><!--#echo var="grid" --> is running <a href="https://sledjhamr.org/mantisbt/project_page.php?project_id=8"><!--#echo var="version" --></a>,
22 part of the <a href="https://sledjhamr.org/git/docs/index.html">SledjHamr</a> project.</p>
23 <p>These statistics will update every two minutes.</p>
24 </body>
25</html>