1
0
mirror of https://github.com/Tha14/toxic.git synced 2025-06-26 19:46:45 +02:00

Compare commits

...

266 Commits

Author SHA1 Message Date
8fa3f6fd8c Greatly reduce redundant window refreshing
This should substantially reduce CPU usage and possibly fix some
issues with interface jittering/flashing
2021-11-19 15:03:08 -05:00
13337041ce Show friend connection type (UDP/TCP) in friend status bar 2021-11-10 13:27:11 -05:00
41e93adbdb game_chess.c: always use "%s"-style format for printf()-style functions
`ncuses-6.3` added printf-style function attributes and now makes
it easier to catch cases when user input is used in palce of format
string when built with CFLAGS=-Werror=format-security:

    toxic/src/game_chess.c:1633:63: error:
      format not a string literal and no format arguments [-Werror=format-security]
     1633 |         mvwprintw(win, board->y_bottom_bound + 2, x_mid, state->status_message);
          |                                                          ~~~~~^~~~~~~~~~~~~~~~

Let's wrap all the missing places with "%s" format.
2021-11-04 09:44:33 +00:00
34b7c0a0d8 Options parsing code cleanup 2021-11-03 16:23:38 -04:00
bcdec5d624 Improve installation instructions 2021-09-12 16:58:28 +02:00
9e353443c2 Fix outdated help message 2021-08-16 11:14:09 -04:00
d02f3b4acb Fix a graphical bug with the game border on some terminal emulators 2021-07-27 14:15:36 -04:00
f2b1c81279 Fix static build script and update libcurl version 2021-06-28 23:07:03 -04:00
768617a129 Fix small build bug and bump to v0.11.1 2021-06-28 16:04:25 -04:00
8dfd009e0e Some minor fixes for game module 2021-06-28 13:54:25 -04:00
321f694bb8 Add ability to toggle colours in game of life 2021-06-03 14:45:03 -04:00
4514ecd839 Refactor game window sizing
- Add large square and rectangle window types
- Remove forced small windows
- Fit game of life to largest possible window type
- Hide cursor in game of life when game is running
2021-06-03 01:19:49 -04:00
5e67571908 Implement Conway's Game of Life 2021-06-01 23:00:00 -04:00
c293fbe0c7 Add support for game window notifications 2021-05-24 14:30:29 -04:00
93fb84206d Bump to version 0.11.0 2021-05-24 14:30:28 -04:00
35aa6922d6 Show previous window instead of Home after closing a window 2021-05-24 14:30:28 -04:00
7abf6388f8 Add ability to compile without game support 2021-05-24 14:30:28 -04:00
7aeb1a0aac Add networking to game engine / add multiplayer chess 2021-05-24 14:30:27 -04:00
60bdcf0ba5 Begin implementing chess 2021-05-24 14:30:27 -04:00
a623976a0e Add game module (WIP) 2021-05-24 14:30:26 -04:00
556a522637 Update man pages with new IRC server 2021-05-24 13:50:00 -04:00
a2f0f298aa Fix bug showing friends with busy status as online 2021-04-20 13:29:41 -04:00
0fc1d9e994 Add two missing mutex unlocks 2021-03-27 11:50:57 -04:00
80fd3d3b97 Fix buggy behaviour when printing non-ascii characters in Home input field
This line doesn't appear to be doing anything useful - most likely a leftover
from the recent UI overhaul
2021-01-25 22:36:05 -05:00
07a41f9e0b Show previous window instead of Home after closing a window 2021-01-25 17:09:23 -05:00
f643975941 Fix bug preventing the /clear command from working in Home window 2021-01-23 21:17:46 -05:00
a5246bcbc6 Bump to version 0.10.1 2021-01-20 16:18:43 -05:00
b37e28c2d0 Add a script for building minimal static toxic
The intention is that JFreegman would run it locally on his machine for
every Toxic release and publish the build artifacts on GitHub for
download, similarly to how the qTox team makes release builds. This
should mitigate the loss of the Jenkins build server to some extent,
which used to provide Toxic builds before.
2021-01-19 21:48:51 -05:00
973e60ef11 Fix Makefile assignment bug
When a user provides variable=value as an argument to make, all
assignments to that variable are ignored within the Makefile because the
user has explicitly overrode variable to be "value". This made the
ENABLE_ASAN assignment to be ignored, resulting in Toxic always enabling
ASAN unless you run `make ENABLE_ASAN=disabled`, which is not
documented and not how it's intended to work. This can be fixed by
prefixing the assignment with "override", but to be in line with other
argument assignments we just change the variable name.

See more at:
https://www.gnu.org/software/make/manual/html_node/Overriding.html
2021-01-19 20:18:24 -05:00
ae94bc593b Rename some build options for naming consistency 2021-01-19 15:21:31 -05:00
81eb58532e Fix bug sometimes causing join message spam on conference join 2021-01-17 17:10:10 -05:00
8464ea9a7a Fix minor display bug with the audio call info box 2021-01-12 19:18:22 -05:00
b77bff35a1 Add config man entries for colour scheme options 2020-12-06 11:52:46 -05:00
eb964b64c2 Fix off by one error and comment magic numbers 2020-12-05 20:53:09 -05:00
2ff9d29491 Custom colors proof of concept
Fix formatting

Fix repeated variable in example config
2020-12-05 17:46:13 -06:00
2640919318 Don't announce when ghost peers leave the group
This appears to be a toxcore bug, but nonetheless it looks ugly. Also changed
group -> conference for join/part messages
2020-12-03 11:49:48 -05:00
2fcbc4fa1c Remove lots of duplicate code related to printing timestamps 2020-11-30 17:55:57 -05:00
4330bf5867 Show peer join/part messages in conferences
Also a couple trivial fixes
2020-11-30 12:06:13 -05:00
3f1b7cdd26 Update config example and some default settings to reflect recent changes 2020-11-30 00:58:43 -05:00
1e985c1456 Interface improvements and bump to v0.10.0
- Give window tab and statusbar a make over
- Place window tab above input field
- Reduce input field to one square in height
- Refactor window tab so that it's now a subwin of its parent ToxWindow
- Fix bug causing notification counter to sometimes increment by 2
- No longer scroll on output when output is not at bottom of screen
- Show a small indicator on far left of window tab when output is
  not at bottom of screen
- Reduce ncurses/UI thread sleep time by half
- Handle nanosleep errors better
2020-11-29 23:26:51 -05:00
61740bda85 Fix a couple minor issues
Print a warning if remove() fails, and check if the win pointer is null
before using it (this is unnecessary but it's correct and shuts coverity up)
2020-11-26 18:25:37 -05:00
0d8e6d713e Update readme screenshot and bump to v0.9.1 2020-11-26 16:58:09 -05:00
39e4ff8bd6 Fix tab complete bug
If a directory contains a single entry tab complete will no longer fail
2020-11-26 16:20:51 -05:00
0434ac186a Fix bug causing file transfer status bars to be truncated 2020-11-26 16:17:00 -05:00
8d9d51640c Fix display bug caused by noread flag appended to unread messages
A line's line count now increments without messing up the formatting in cases where
the noread flag wraps to the next line.

Additionally, the way noread flags are handled has been refactored and made
more efficient.
2020-11-26 16:16:59 -05:00
c4c0c0d1f4 Another logging fix
When renaming a log file, if the new name already exists we just
start appending to it and delete the old file. There's no need
to create a backup.

This fixes an issue where leaving and rejoining the same group
multiple times will eventually lead to the logger not working
due to trying to use the same file name over and over again.
2020-11-26 16:10:59 -05:00
3f2826bd66 Add release and LLVM asan build options
The release build uses -O2 and flto, and has no debug symbols.
-Wmissing-field-initializer was removed due to false positives
on newer versions of clang
2020-11-26 16:04:54 -05:00
7b7ea0e386 Fix small regression
This wasn't supposed to make it into the commit
2020-11-24 23:42:25 -05:00
d35a38735b Fix line wrapping/padding issues
It should now parse messages with newlines properly as well as messages that
contain long sequences of characters with no spaces
2020-11-24 19:47:04 -05:00
f0c4906fdc Fix some off by one bugs with line scrolling and some cleanup
Instead of trying (and failing) to correctly calculate the number of lines on the
screen a given message will need space for, we dynamically set the value both
on initiation of a new line, and whenever that line is printed to the screen
using the print_wrap() function.

This should fix the screen occasionally not scrolling down the correct number of
lines when a new message is printed.
2020-11-24 19:36:03 -05:00
56ba61e061 Cleanup and refactor x11 code
- Deleted X11 code for an unfinished/broken feature
- Rename xtra -> x11focus
- Cleanup the remaining code a bit
2020-11-24 19:31:59 -05:00
898d89e95a Properly handle failures to convert widechar strings to multibyte 2020-11-24 16:41:42 -05:00
1fd1e27bdf Remove seconds from default timestamp
Also reduce the --- prefix to one dash before a message. These characters are taking
up valuable real-estate
2020-11-22 18:53:32 -05:00
8e84ac58d4 Implement word wrapping
It's about time
2020-11-22 18:41:12 -05:00
9d65997871 Add notification counters to bottom tab
A counter now increments in the bottom bar for unfocused windows showing how
many unread messages are pending. Tabs with no pending messages show [*]
instead of their index (showing the index is useless and somewhat confusing)
2020-11-21 11:25:41 -05:00
da2889f3ab Fix UI regression
The conference number needs to be displayed in the tab name
so that you can invite friends to conferences
2020-11-20 02:10:52 -05:00
312b38d253 A few small fixes
- Fix race condition in draw_peer()
- Handle realloc_peer_list() error
- Remove dead code in cmd_conference()
- Reduce scope of a few variable variables
- Fix possible buffer truncation in api.c
2020-11-19 14:23:32 -05:00
0554bf0240 Create backup of duplicate log file instead of deleting it
This case should never occur, but just in case it does it's good to handle it
without any data loss
2020-11-19 14:23:31 -05:00
53a7530e8a Some UI improvements
- Bottom tab now only shows indices of active chat windows unless focused
- Always focus Home screen on startup instead of the last loaded conference
- Conference tab names are no longer prefixed with the conference number
- Home and Contact tab names are now capitalized
2020-11-19 01:30:30 -05:00
41be04a142 Implement push-to-talk for conference audio 2020-11-18 16:57:26 -05:00
31f36318a2 Add config settings for audio channels 2020-11-18 16:29:01 -05:00
f882fdf608 Use two channels for conference audio 2020-11-17 16:31:17 -05:00
7e1e410307 Refactor logging
- Conference logging now behaves the same as 1-on-1 chats: Instead
  of creating a new log file every time we restat the client
  we use the unique conference ID to keep track of path names.
  This also allows us to load history for saved groups on client startup

- Added a log init function / general code refactor.

- Fixed a bug that caused log files to be created even when logging
  is disabled.
2020-11-17 16:05:20 -05:00
c135c812c2 Fix playing unallocated memory on opening audio device 2020-11-16 00:00:00 +00:00
6c239193ab Fix bug sometimes preventing typing status from being updated 2020-11-15 12:30:26 -05:00
de7db08352 Bump to version 0.9.0 2020-11-14 14:30:56 -05:00
ba5ded9bc2 Use compound literals to zero out structs instead of memset 2020-11-14 00:00:05 +00:00
4581dee4fc astyle 2020-11-14 00:00:05 +00:00
d75d6e8b60 sort includes 2020-11-14 00:00:05 +00:00
142ce642f0 Per-call pending status and state handling
* Proper handling of incoming video calls.
* Add command /vcall for initiating video calls
* Add command /res command to set video resolution
2020-11-14 00:00:10 +00:00
7dead5ec96 Implement simplistic VAD 2020-11-14 00:00:01 +00:00
ddcf224db2 Implement groupAV 2020-11-14 00:00:01 +00:00
daf794c4a2 Rework audio device handling
We now have at most one input and one output device open at any time,
but can have multiple capture callbacks and multiple output sources.
2020-11-14 00:00:01 +00:00
dac0124f0f Fix some issues with file transfers
- Fix bug causing failed avatar transfers to be sent as normal file transfers
      when a friend goes offline and comes back online
- Remove some unused members of the FileTransfer struct
- Rename filenum -> filenumber and friendnum -> friendnumber
2020-11-13 19:38:33 -05:00
15b7a30925 Fix some bugs/issues with the message queue
- It's no longer possible for messages to be sent out of order
- Check if logger is enabled before doing pointless API calls
- Fix linked list bug (a part of the code almost never executes)
2020-11-12 11:39:19 -05:00
77ab71f26f Refactor message queue
We now attempt to send all queued messages per call to cqueue_try_send() instead
of just the oldest message in the queue. This speeds things up substantially.

Also fixed a very unlikely bug where the read receipt might wrap around to zero
which we used as a reserved value for an unsent message
2020-11-11 16:28:50 -05:00
68e1ba312d Use compound literals to zero out structs instead of memset
This is cleaner and much less prone to bugs
2020-11-10 21:25:27 -05:00
752fc6d619 Fix race condition when window is closed with active notifications
A ToxWindow's notifications need to be halted before the window is freed
2020-11-10 17:20:40 -05:00
16bcb27ca7 Update openal version for macos travis build 2020-11-09 17:29:19 -05:00
71d7d355a6 Use enum to identify window types instead of bool variables 2020-11-09 17:01:22 -05:00
4188b392cc Rename: groupchats -> conferences
This is in line with the toxcore API naming scheme and is in preparation
for the merge with the new groupchat implementation
2020-11-08 10:08:24 -05:00
811fbfbb1e Fix char -> widechar comparisons 2020-11-08 00:07:28 -05:00
32eb7d3040 Filter invalid file names for inbound file transfers 2020-11-05 10:55:48 -05:00
42763905d7 Replace deprecated usleep function with nanosleep
usleep was declared obsolete in POSIX.1-2001
2020-11-04 22:05:59 -05:00
f64300d1d6 Close file handlers when inbound file transfer fails 2020-11-03 13:26:23 -05:00
1a723f0e8e Fix bad behaviour with duplicate file names
If the new path is too long for the buffer we cancel the file transfer
and return an error instead of truncating the file name and continuing.
2020-11-03 13:03:47 -05:00
a86884c40e Remove legacy code and fix a couple format specifiers
This code was added over 5 years ago and no longer serves a purpose
2020-11-03 01:00:08 -05:00
3f02e119f4 add missing malloc error checks and fix widechar to char comparison 2020-11-03 00:59:28 -05:00
1bbd50aac7 Fix a few issues
- realloc needs to be error checked
- use correct format specifiers
- make sure optarg and DATA_FILE aren't null before using them
2020-11-02 18:08:54 -05:00
e7a0c32a68 Refactor a few complex functions
Also moved some single use functions from misc_tools to their
respective files
2020-11-01 15:55:07 -05:00
7560bc9547 modularize string arrays for line completion
Instead of using various different forms of string arrays and having to handle them
differently for string completion, we now always use char pointer arrays. This allows
us to remove some large stack allocations, remove a bunch of confusing defines that
keep track of global array sizes, and generally unclutters the code so it's easier
to read.
2020-11-01 11:29:51 -05:00
2b43340c90 Convert all variable length arrays to heap allocations
VLA's are inherently unsafe so the safest option is to not use them
2020-11-01 11:29:48 -05:00
ff1620c923 fix invalid usage of stderr and incorrect formatting 2020-11-01 11:19:18 -05:00
1303053a27 Use c99 standard instead of gnu99
No gnu99 extensions are used or will be used
2020-10-29 19:29:28 -04:00
91f194c821 Fix pointer use after free bug
If toxcore fails to end a call we still need to do a cleanup
2020-10-28 23:59:28 -04:00
478762f76c Dynamically allocate audio frame buffer memory 2020-10-28 23:59:25 -04:00
4d96d6a753 Fix regression related to https lookups
Reducing the buffer size of HTTPS responses broke DHT nodelist fetching. This change
puts the old buffer size back and converts all stack allocations of the read buffer
to heap allocations.
2020-10-27 12:34:12 -04:00
3cdcfbf4e5 Reduce size of some large stack memory allocations 2020-10-25 13:48:14 -04:00
4c302da503 Fix a bunch of Wformat-truncation warnings due to snprintf() misuse 2020-10-24 14:44:41 -04:00
26b5fe8f9d fix: Fix incorrect file specified error when building in WSL from CLion 2020-10-24 12:52:43 +02:00
22d60232fb Bump to version 0.8.4 2020-10-22 22:14:33 -04:00
e428879beb Fix clashing key bindings
Scrolling the groupchat peer list now uses ctrl+up and down
2020-10-13 19:52:43 -04:00
3015138a5a Manually attempt to decode input char sequences
This is currently a fallback method for when the terminal doesn't
detect ctrl arrow sequences, but it is generalized for future additions
2020-10-13 16:12:55 -04:00
9c06ad608b Add ability to skip words in input field with ctrl-left/right arrow 2020-10-10 11:21:15 -04:00
015dbd9a96 fix: Fix non-working bell notifications
The problem with the bell notifications only happens when Toxic is
compiled with X11 support, but does not run under X. This commit changes
toxic's behavior such that it behaves identically when not running under an
X session, no matter if it has been compiled with X11 support or not.
2020-08-24 16:36:52 +02:00
a7466c3142 Add option to disable LAN 2020-08-10 21:56:45 -04:00
f012007cc4 A couple small fixes 2020-05-19 21:44:35 -04:00
dcf3baf60f Make sure logger file is closed after tox_kill 2020-05-18 13:58:55 -04:00
4bda799a4b Add the ability to log to file (#84) 2020-05-16 23:12:54 -04:00
bdeae33d48 Add macOS build.
Lots of features disabled, because I can't get it to work yet. We'll
enable features one by one as we get them to work.
2020-05-05 00:47:08 +01:00
47591d5298 chore: Use global release-drafter config. 2020-05-05 00:25:10 +01:00
b5ace27a3e chore: Remove astyle from travis build.
The astyle check is now done by restyled.
2020-05-04 23:50:31 +01:00
b334622d36 Add release-drafter config. 2020-05-04 22:01:33 +01:00
4bfb344caa Add option for toxcore logging in stderr 2020-05-04 16:15:28 -04:00
16d96d6faf Add restyled config for toxic. 2020-05-04 20:54:58 +01:00
0ab2bad226 Add CODEOWNERS and settings.yml files. 2020-04-25 03:40:02 +00:00
68db926f9f Check that files are formatted correctly.
Fail the CI build if they are not.
2020-04-23 01:16:57 +00:00
b270c1e8b7 Remove extra semicolon in lock/unlock macros.
lock/unlock are always expanded in a context followed by a semicolon.
2020-04-23 01:12:34 +00:00
e7142e49fd Terminate notify before windows
This fixes a use after free bug
2020-04-22 20:54:53 -04:00
610906d07f fix segfault on starting video 2020-04-17 00:00:00 +00:00
6f72a191ba Revert "Add feature flags and missing #includes."
This reverts commit dd5fa236ae.

Also, set `-std=gnu99` in Bazel build.
2020-04-17 19:37:22 +00:00
dd5fa236ae Add feature flags and missing #includes.
These are needed to compile on some strict Linuxes.
2020-04-15 21:34:30 +00:00
51e1ab94b3 Add QR disabling options to INSTALL.md 2020-04-04 23:20:52 -03:00
ddc8c53abf Fix unused variable warnings and change all (void)s to UNUSED macro 2020-03-30 16:02:27 -04:00
46513017e3 Half-hearted attempt at making toxic work on osx.
Video has no chance without X11 for now.
2020-03-30 18:14:43 +00:00
98cb7f58c0 Fix unused parameter and unused result warnings 2020-03-15 14:57:00 -04:00
206bf407fd Use rules_cc instead of native cc_binary rules. 2020-03-12 12:23:57 +00:00
0a8ac4de3b Fix yamllint warning about missing document start marker.
YAML apparently likes to have this marker, so let's have it.
2020-03-10 14:57:12 +00:00
87d54acad0 Add sponsorship button 2020-03-07 13:19:10 -05:00
45ff6d8bac Migrate to Camel_Case enum types.
UPPER_CASE enum types are deprecated and will be removed in 0.3.0.
2020-03-07 18:11:41 +00:00
437dd8baeb Some misc fixes 2020-02-29 14:14:56 -05:00
b080236ee5 Fix typo 2020-02-10 17:16:56 -05:00
116bff8cef format fixes 2019-12-07 23:17:02 -05:00
ddeca171a0 Add input line text to history on UP and DOWN 2019-12-07 18:29:33 -03:00
127f9462e0 make friend add fail if friend is in the block list 2019-11-16 01:55:47 -05:00
4b5a9abbd4 Fix A/V bug preventing the caller from being properly informed of call termination 2019-07-12 11:51:08 -04:00
bb2257973e Load conference titles on startup for saved conferences (#43) 2019-06-30 14:51:13 -04:00
12b9cd2386 Fix header issues 2019-03-27 01:38:15 -04:00
2cbe8fa880 Use correct integer types to fix groupchat peer scrolling bug 2019-03-26 23:05:24 -04:00
2e39bee05a Check for openal lib before attempting to build with video support 2019-03-15 16:05:54 -04:00
05eda76643 Partially fix autocomplete behaviour for input with spaces 2019-02-17 17:25:34 -05:00
f7b73af9a7 Add config option for the notification timeout duration 2018-11-10 08:54:59 -05:00
73aaa44d12 Add config option to set data file auto-save frequency 2018-10-30 18:22:30 -04:00
e4abd8b36b Bump version to 0.8.3 2018-10-30 18:03:44 -04:00
9e3d4f3889 Check if pointer is null before accessing 2018-10-28 07:09:37 -04:00
b7d67c1d86 re-init AV for unblocked contacts 2018-10-20 02:56:37 -04:00
c4a11f8dc7 Refactor ToxWindows
ToxWindow constructor functions now return pointers instead of structs
ToxWindow windows array now holds pointers instead of structs
Refactor some logic in windows.c pertaining to keeping track of active windows
Use uint8_t for window array indexing
2018-10-19 17:43:06 -04:00
d18cc8cbc2 Fix two memory leaks and properly clean up friendlist window 2018-10-19 17:43:03 -04:00
ce6d4861fb Fix bug where autocomplete added a forward slash to files with no extension
Also slightly refactored the line complete functions
2018-10-17 18:00:09 -04:00
8f0e6026f0 Add enum for file_type() and a little cleanup 2018-10-17 18:00:08 -04:00
258736995d Remove ability to set note with status command 2018-10-17 18:00:08 -04:00
56e03a3f8b No longer require quotes to enclose paths 2018-10-17 18:00:08 -04:00
b6c746b5f5 No longer require quotes for commands that take strings as arguments 2018-10-17 18:00:07 -04:00
03673cbced Improve readability of INSTALL.md 2018-10-11 14:22:37 -04:00
0fea930c24 Convert deprecated uppercase enums to camelcase 2018-10-10 13:34:17 -04:00
94d22a8853 Allow empty status messages 2018-10-09 18:21:05 -04:00
63cc23401a Improve readability of defines 2018-10-08 13:39:04 -04:00
f90a774470 Make Toxic easier to port 2018-10-03 22:00:43 +00:00
e7c5fbc873 Cleanup xtra.c
Privatise some functions that don't need to be public
Fix struct names starting with _ char
Remove unnecessary memset of Xtra struct in init_xtra()
2018-09-27 03:41:58 -04:00
d62902ffb3 Use (void) in prototypes instead of ().
Also, removed `__inline` from an extern function (why was it there?).
Also, moved one extern declaration to a header file. There are lots of
these that need to be moved, but one thing at a time.
2018-09-13 23:47:47 +00:00
bebff3be0e Use (void) for empty parameter list in C. 2018-09-08 17:23:07 +00:00
2be4847b53 Ignore warning about unused result.
These should be fixed, but for now we leave it as warning, not error.
2018-09-06 13:46:24 +00:00
4557614443 Enable .travis.yml check and use non-markdown license. 2018-08-17 23:49:01 +00:00
5b30ecf2e4 Use @x11 instead of the longer @x11//:X11 to link against xlib. 2018-08-14 09:55:54 +00:00
2413ad2b59 Use bazel import libs instead of global -l flags.
Import libs currently also point to the global system installed versions,
but at least we have the freedom to import them properly later.
2018-08-05 19:17:57 +00:00
52855b805a Add astyle check to travis. 2018-07-18 17:19:43 +00:00
20b5e46850 Rename callback implementations to be of the form on_$event.
So it's easy to map from implementation to the event without looking at
the `tox_callback_*` calls.
2018-07-18 17:06:28 +00:00
f2b796940e Add missing braces to if/for/while blocks.
Astyle doesn't quite catch all of them.
2018-07-18 15:56:21 +00:00
a37bf300f9 Update astylerc to match toxcore & reformat all source files 2018-07-18 11:33:16 -04:00
cb524dcbc3 Merge branch 'iphydf-local-libconfig' 2018-07-16 10:18:44 -04:00
4144b868ce Use libconfig built in the WORKSPACE rather than system installed.
Improves hermeticity and reproducibility.
2018-07-15 15:34:19 +00:00
af11f16bef fix merge mistake take 2 2018-07-07 10:29:12 -04:00
1d27a496ef fix merge mistake 2018-07-06 15:43:32 -04:00
32bd9dc1a7 Merge branch 'TokTok-master' 2018-07-06 12:35:46 -04:00
3cd2bc7e3c Don't compile A/V code if AUDIO/VIDEO are not #defined. 2018-07-06 15:46:12 +00:00
43ca840658 Conditionally compile api.c and python_api.c contents.
Based on `-DPYTHON`, instead of based on which files are listed in the
source list. This simplifies people's lives when compiling from the
command line with `cc *.c`.
2018-07-06 14:42:33 +00:00
685837357b Add bazel build file for toxic. 2018-07-06 14:41:16 +00:00
812a13b0fb Merge branch 'fx-carton-master' 2018-06-17 20:27:57 -04:00
641fa471d2 Merge branch 'master' of https://github.com/fx-carton/toxic into fx-carton-master 2018-06-17 20:23:12 -04:00
8d5755f2d8 Merge branch 'nieldk-master' 2018-06-17 20:21:05 -04:00
af510b6666 Merge branch 'master' of https://github.com/nieldk/toxic into nieldk-master 2018-06-17 20:09:14 -04:00
46f646afcf Add an option to disable qr codes 2018-06-16 05:07:16 +02:00
68ce17a57f Temp fix for nick change spam on group join 2018-04-19 22:23:49 -04:00
a69fad15c1 Use default timestamp format if provided one is invalid 2018-04-10 17:47:21 -04:00
7621fe9a62 fix macOS build (#494) 2018-03-31 21:10:17 -04:00
f6d9bc3a74 Update check_features.mk
Changed cfg/checks/check_features.mk to enable individual Audio/Video checks by adding proper DISABLE_VI option which takes "0" or "1" for disable/enable video checks/building.
2018-03-19 22:31:31 +01:00
29aea0b42c Remove libtoxav from install instructions as it's now part of the toxcore lib 2018-03-03 23:55:54 -05:00
815c29ee31 Significantly reduce time before trying to re-send a message with no read receipt
(also some formatting cleanup)
2018-03-03 23:54:12 -05:00
3917f664bf Bump to v0.8.2 2018-03-01 19:14:14 -05:00
a223329815 Fix persistent groupchat loading 2018-02-27 18:46:56 -05:00
3fec11d5f9 Update readme 2018-02-26 03:03:12 -05:00
221edb0012 add a .travis file and some build fixes 2018-02-26 02:34:38 -05:00
2710ab6034 Re-implement group nick change notifications 2018-02-25 03:43:19 -05:00
bc3ffac0ba re-add group peer list sorting 2018-02-25 01:21:22 -05:00
29f55c5277 Update API
-Removed usage of deprecated API functions
-Integrated conference changes with a few regressions
2018-02-25 00:00:06 -05:00
a290f0f7f8 Update toxav API calls 2018-02-24 15:38:07 -05:00
5cd196a769 Fix autocomplete bug
Formatting was messed up due to not accounting for null byte in directory list
2018-02-20 20:54:26 -05:00
b14d983a8c Merge branch 'iphydf-opaque-options' 2018-01-20 15:20:25 -05:00
51f1daeec8 Treat Tox_Options as opaque struct: no deref, no alloc.
We allocate it inside toxcore and dereference it inside as well. This
allows us to change the layout of that struct and add new members.
2018-01-20 19:51:13 +00:00
b799c6a8d7 Merge branch 'iphydf-fix-typedefs' 2018-01-20 13:34:53 -05:00
b9f9546e2b Fix typedef enums and potential uninitialised value. 2018-01-20 18:30:35 +00:00
846bc4613e Fix comment 2017-11-20 07:07:24 -05:00
a5a1f6015d Merge branch 'calls_fix' 2017-11-20 07:03:22 -05:00
fe6a7074ea Dynamically allocate audio calls structure
This fixes a bug that caused a segfault when attempting to call a friend with a
friend number above the MAX_CALLS limit
2017-11-20 07:02:56 -05:00
db7c9fe426 Merge branch 'master' of https://github.com/JFreegman/toxic 2017-11-17 18:17:47 -05:00
b1d8ab102f Bump to version 0.8.1 2017-11-17 17:44:56 -05:00
0bd5b4ddee Repair operating system checks (#474) 2017-11-16 16:59:51 -05:00
c387df35f8 Merge branch 'f8l-pkgsrc' 2017-11-16 16:03:20 -05:00
351a50c214 Do not assume what is not Linux or a BSD descendant is OSX 2017-11-15 21:02:10 +01:00
93175314b5 Merge branch 'aSourceFish-master' 2017-11-15 02:25:55 -05:00
b905a1a3c5 One more bit of standard... 2017-11-15 05:56:13 +02:00
c4386b195f A bit of standard... 2017-11-15 05:54:46 +02:00
ed1e617380 Make Toxic compile at NetBSD, STAGE 2 2017-11-15 05:50:00 +02:00
1382adb1f6 Make Toxic compile at NetBSD, STAGE 1 2017-11-15 05:46:47 +02:00
ecf1c317b7 Make Toxic compile at NetBSD, STAGE 0 2017-11-15 05:24:19 +02:00
cf0b99f1e5 Merge branch 'aramchandran-master' 2017-11-13 15:37:40 -05:00
3605a296a9 Update install.mk 2017-11-13 13:30:22 -05:00
9375d220f9 Merge branch 'aSourceFish-master' 2017-11-11 16:51:34 -05:00
8f94b0a218 Shut down the warning at *BSD 2017-11-11 21:17:34 +02:00
85a0becbf9 Make Toxic compile at OpenBSD 2017-11-11 21:04:45 +02:00
fec36ad9e6 Make Toxic compile at OpenBSD 2017-11-11 21:03:28 +02:00
ecdf6f01d2 Merge branch 'EnniRosario-refresh_devices' 2017-10-31 16:24:53 -04:00
e1bfa30769 Refresh device list on /lsdev. 2017-10-31 18:08:06 +02:00
ebcbc7497b Indicate selected device when printing. 2017-10-31 17:45:39 +02:00
e844ece28b Merge branch 'zugz-reduceRedrawing' 2017-10-29 16:00:46 -04:00
8508451ba6 avoid unnecessary redrawing 2017-10-29 16:50:42 +01:00
5cc83a7cb5 Merge branch 'patheticpat-fix-tmux' 2017-09-05 17:43:13 -04:00
febc725763 Fix tmux detached detection
The old code failed if the session had a custom name instead of the
default numeric id. To be safe in both cases, look for the session_id in
the tmux list-sessions output.
2017-09-05 12:49:48 +02:00
f2c116feb3 Fix potential string truncations with snprintf 2017-09-01 17:36:17 -04:00
52dd60dc86 Fix potential int truncation and double-check lengths before copy 2017-08-28 19:37:19 -04:00
80c0500299 Fix formatting bug caused by strings containing \r 2017-08-28 18:26:52 -04:00
ab490d28b4 Merge branch 'avoidr-status_cmd_feedback' 2017-08-28 17:13:53 -04:00
a9f7f85617 /status: give feedback to user on status change 2017-08-27 12:27:04 +02:00
1bfc1ba371 Merge branch 'Dako300-master' 2017-07-07 20:03:07 -04:00
2ede39369a added /bitrate 2017-07-07 23:47:59 +00:00
922c184195 Only include python libs when necessary 2017-06-06 19:03:40 -04:00
56a9571509 Make sure message id fits inside a signed int 2017-06-02 01:15:44 -04:00
0136f22076 Fix UI bugs & format
This fixes a bug where lines would sometimes be incorrectly marked as unread, as well as
a bug where inbound messages would sometimes be coloured incorrectly
2017-06-01 16:46:12 -04:00
c4ace288af Bump to v0.8.0 2017-05-24 15:10:50 -04:00
6d3fbfee59 Merge branch 'TsarFox-master' 2017-05-24 14:57:32 -04:00
=
be5e7906da Fix fortune.py input issues 2017-05-24 10:10:18 -04:00
369f26932e Fix error messages being printed when autorun_path is unset. 2017-05-23 19:12:53 -04:00
22ea522baf Add "/run" to autocompleter and fix example Python script. 2017-05-22 20:47:56 -04:00
4f60d546e6 Disable Python support in the default build 2017-05-21 16:18:39 -04:00
76d1eafdc0 Change visibility on Python commands list 2017-05-20 08:18:28 -04:00
37912f2d88 Update INSTALL.md 2017-05-19 21:09:29 -04:00
09710327c5 Merge branch 'Dako300-master' 2017-05-19 20:05:52 -04:00
acee4615f8 double signal handler prevents accidental closes 2017-05-19 14:38:39 -05:00
5ed26eda9b Added constants to the Python interface for calls to execute. 2017-05-19 15:35:31 -04:00
6d2b90ac9f Error reporting on failure of invoke_autoruns and solved issue with call to Py_FinalizeEx 2017-05-18 21:37:28 -04:00
02ea0fac44 get_status returns a string rather than an integer. 2017-05-17 08:39:29 -04:00
7d3d129624 Updated code style 2017-05-16 23:19:39 -04:00
b3ed8bc35c Finalized and documented the Python scripting interface. 2017-05-16 20:31:23 -04:00
90210daca7 Python interface for getting the user's nickname/status and executing commands 2017-05-15 22:12:28 -04:00
0e13a1f1bc Implemented a basic Python scripting engine, optional compilation. 2017-05-15 20:29:00 -04:00
09e2690211 Fixed INSTALL formatting (#443) 2017-04-12 12:00:43 -04:00
e65e3af274 Merge branch 'peetcamron-fix/fflush-for-musl' 2017-01-28 00:45:28 -05:00
c6c60d018e Added some fflush after the printf with no \n so we don't have a blank
screen when compiled with musl.
2017-01-28 00:06:03 -05:00
451d4ced80 Add option that prints toxic and toxcore version 2017-01-25 22:13:59 -05:00
7a7402ff86 Update text files with new toktok toxcore link 2016-12-21 17:08:32 -05:00
600e013adc Bump to version 0.7.2 2016-12-18 00:11:24 -05:00
1d71e2eb18 Port to toktok-c-toxcore branch 2016-12-18 00:01:51 -05:00
f858714edd Fix small nodeslist parsing bug 2016-10-23 12:26:18 -04:00
4df44a7274 Merge branch 'tharvik-master' 2016-10-18 12:21:58 -04:00
a26ed9d28f add missing implicit include 2016-10-18 09:38:28 +02:00
2bd5083b8f Corrected brew errors (#413)
Corrected brew errors
2016-10-07 13:24:31 -04:00
128 changed files with 19010 additions and 5904 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1 @@
/.github/ @TokTok/admins

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
---
github: [JFreegman]

17
.github/settings.yml vendored Normal file
View File

@ -0,0 +1,17 @@
---
_extends: .github
repository:
name: toxic
description: An ncurses-based Tox client
topics: tox, console, chat
branches:
- name: "master"
protection:
required_status_checks:
contexts:
- Codacy/PR Quality Review
- CodeFactor
- Travis CI - Pull Request
- code-review/reviewable

4
.gitignore vendored
View File

@ -16,3 +16,7 @@ stamp-h1
build/toxic
build/*.o
build/*.d
apidoc/python/build
*.vim
*.tox
*.nvim*

4
.restyled.yaml Normal file
View File

@ -0,0 +1,4 @@
---
restylers:
- astyle:
arguments: ["--options=astylerc"]

60
.travis.yml Normal file
View File

@ -0,0 +1,60 @@
---
language: python
python: nightly
dist: xenial
os: linux
jobs:
include:
- env: JOB=linux
addons:
apt:
packages:
- libalut-dev
- libconfig-dev
- libnotify-dev
- libopenal-dev
- libopus-dev
- libqrencode-dev
- libvpx-dev
cache:
directories:
- $HOME/cache
install:
# Where to find libraries.
- export LD_LIBRARY_PATH=$HOME/cache/usr/lib
- export PKG_CONFIG_PATH=$HOME/cache/usr/lib/pkgconfig
# c-sodium
- git clone --depth=1 --branch=stable https://github.com/jedisct1/libsodium ../libsodium
- test -f $HOME/cache/usr/lib/libsodium.so || (cd ../libsodium && ./configure --prefix=$HOME/cache/usr && make install -j$(nproc))
# c-toxcore
- git clone --depth=1 https://github.com/TokTok/c-toxcore ../c-toxcore
- test -f $HOME/cache/usr/lib/libtoxcore.so || (cd ../c-toxcore && cmake -B_build -H. -DCMAKE_INSTALL_PREFIX:PATH=$HOME/cache/usr && make -C_build install -j$(nproc))
script:
- make ENABLE_PYTHON=1 -j$(nproc)
- env: JOB=macos
os: macos
language: c
cache:
directories:
- $HOME/cache
install:
- brew install
freealut
libconfig
libqrencode
libsodium
openal-soft
- export LDFLAGS="-L/usr/local/Cellar/openal-soft/1.21.0/lib"
- git clone --depth=1 https://github.com/TokTok/c-toxcore ../c-toxcore
- test -f /usr/local/lib/libtoxcore.dylib || (cd ../c-toxcore && cmake -B_build -H. && make -C_build install -j$(nproc))
script:
- make ENABLE_PYTHON=1 DISABLE_DESKTOP_NOTIFY=1 DISABLE_X11=1 DISABLE_AV=1 DISABLE_SOUND_NOTIFY=1 -j$(nproc)

41
BUILD.bazel Normal file
View File

@ -0,0 +1,41 @@
load("@rules_cc//cc:defs.bzl", "cc_binary")
load("//tools/project:build_defs.bzl", "project")
project()
cc_binary(
name = "toxic",
srcs = glob(
[
"src/*.c",
"src/*.h",
],
exclude = ["src/video*"],
) + select({
"//tools/config:linux": glob(["src/video*"]),
"//tools/config:osx": [],
}),
copts = [
"-std=gnu99",
"-DAUDIO",
"-DPACKAGE_DATADIR='\"data\"'",
"-DPYTHON",
"-DQRCODE",
] + select({
"//tools/config:linux": ["-DVIDEO"],
"//tools/config:osx": [],
}),
deps = [
"//c-toxcore",
"@curl",
"@libconfig",
"@libqrencode",
"@libvpx",
"@ncurses",
"@openal",
"@python3//:python",
] + select({
"//tools/config:linux": ["@x11"],
"//tools/config:osx": [],
}),
)

View File

@ -1,70 +1,71 @@
# Installation
* [Dependencies](#deps)
* [OS X Notes](#deps_osx)
* [Dependencies](#dependencies)
* [OS X Notes](#os-x-notes)
* [Compiling](#compiling)
* [Documentation](#docs)
* [Documentation](#documentation)
* [Notes](#notes)
* [Compilation variables](#comp_vars)
* [Packaging](#packaging)
* [Compilation variables](#compilation-variables)
* [Environment variables](#environment-variables)
<a name="deps" />
## Dependencies
| Name | Needed by | Debian package |
|------------------------------------------------------|----------------------------|---------------------|
| [Tox Core](https://github.com/irungentoo/toxcore) | BASE | *None* |
| [Tox Core](https://github.com/toktok/c-toxcore) | BASE | libtoxcore-dev |
| [NCurses](https://www.gnu.org/software/ncurses) | BASE | libncursesw5-dev |
| [LibConfig](http://www.hyperrealm.com/libconfig) | BASE | libconfig-dev |
| [GNUmake](https://www.gnu.org/software/make) | BASE | make |
| [libcurl](http://curl.haxx.se/) | BASE | libcurl4-openssl-dev|
| [libqrencode](https://fukuchi.org/works/qrencode/) | BASE | libqrencode-dev |
| [Tox Core AV](https://github.com/irungentoo/toxcore) | AUDIO | *None* |
| [libqrencode](https://fukuchi.org/works/qrencode/) | QRCODE | libqrencode-dev |
| [OpenAL](http://openal.org) | AUDIO, SOUND NOTIFICATIONS | libopenal-dev |
| [OpenALUT](http://openal.org) | SOUND NOTIFICATIONS | libalut-dev |
| [LibNotify](https://developer.gnome.org/libnotify) | DESKTOP NOTIFICATIONS | libnotify-dev |
| [Python 3](http://www.python.org/) | PYTHON | python3-dev |
| [AsciiDoc](http://asciidoc.org/index.html) | DOCUMENTATION<sup>1</sup> | asciidoc |
<sup>1</sup>: see [Documentation](#docs)
<a name="deps_osx" />
<sup>1</sup>: see [Documentation](#documentation)
#### OS X Notes
Using [Homebrew](http://brew.sh):
```
brew install openal-soft freealut libconfig
brew install https://raw.githubusercontent.com/Tox/homebrew-tox/master/Formula/libtoxcore.rb
brew install https://raw.githubusercontent.com/Homebrew/homebrew-x11/master/libnotify.rb
brew install curl qrencode openal-soft freealut libconfig libpng
brew install --HEAD https://raw.githubusercontent.com/Tox/homebrew-tox/master/Formula/libtoxcore.rb
brew install libnotify
export PKG_CONFIG_PATH=/usr/local/opt/openal-soft/lib/pkgconfig
make
```
You can omit `libnotify` if you intend to build without desktop notifications enabled.
<a name="Compiling">
## Compiling
```
make PREFIX="/where/to/install"
sudo make install PREFIX="/where/to/install"
make
sudo make install
```
<a name="docs" />
#### Documentation
Run `make doc` in the build directory after editing the asciidoc files to regenerate the manpages.<br />
**NOTE FOR DEVELOPERS**: asciidoc files and generated manpages will need to be commited together.<br />
**NOTE FOR EVERYONE**: [asciidoc](http://asciidoc.org/index.html) (and this step) is only required for regenerating manpages when you modify them.
**Note for developers**: asciidoc files and generated manpages will need to be committed together.<br />
**Note for everyone**: [asciidoc](http://asciidoc.org/index.html) (and this step) is only required for regenerating manpages when you modify them.
<a name="notes" />
## Notes
<a name="comp_vars" />
#### Compilation variables
* You can add specific flags to the Makefile with `USER_CFLAGS=""` and/or `USER_LDFLAGS=""`
* You can pass your own flags to the Makefile with `CFLAGS=""` and/or `LDFLAGS=""` (this will supersede the default ones)
* Additional features are automatically enabled if all dependencies are found, but you can disable them by using special variables:
* `DISABLE_X11=1`build toxic without X11 support (needed for focus tracking)
* `DISABLE_AV=1` → build toxic without audio call support
* `DISABLE_SOUND_NOTIFY=1` → build toxic without sound notifications support
* `DISABLE_DESKTOP_NOTIFY=1` → build toxic without desktop notifications support
* You can add specific flags to the Makefile with `USER_CFLAGS=""` and `USER_LDFLAGS=""` passed as arguments to make, or as environment variables
* Default compile options can be overridden by using special variables:
* `DISABLE_X11=1` → Disable X11 support (needed for focus tracking)
* `DISABLE_AV=1`Disable audio call support
* `DISABLE_SOUND_NOTIFY=1` → Disable sound notifications support
* `DISABLE_QRCODE=1` → Disable QR exporting support
* `DISABLE_QRPNG=1` → Disable support for exporting QR as PNG
* `DISABLE_DESKTOP_NOTIFY=1` → Disable desktop notifications support
* `DISABLE_GAMES=1` → Disable support for games
* `ENABLE_PYTHON=1` → Build toxic with Python scripting support
* `ENABLE_RELEASE=1` → Build toxic without debug symbols and with full compiler optimizations
* `ENABLE_ASAN=1` → Build toxic with LLVM Address Sanitizer enabled
<a name="packaging" />
#### Packaging
* For packaging purpose, you can use `DESTDIR=""` to specify a directory where to store installed files
* `DESTDIR=""` can be used in addition to `PREFIX=""`:
* `DESTDIR=""` is meant to specify a directory where to store installed files (ex: "/tmp/build/pkg")
* `PREFIX=""` is meant to specify a prefix directory for binaries and data files (ex: "/usr/local")
* `DESTDIR=""` Specifies the base install directory for binaries and data files (e.g.: DESTDIR="/tmp/build/pkg")
#### Environment variables
* You can use the `CFLAGS` and `LDFLAGS` environment variables to add specific flags to the Makefile
* The `PREFIX` environment variable specifies a base install directory for binaries and data files. This is interchangeable with the `DESTDIR` variable, and is generally used by systems that have the `PREFIX` environment variable set by default.<br />
**Note**: `sudo` does not preserve user environment variables by default on some systems. See the `sudoers` manual for more information.

View File

View File

@ -3,39 +3,52 @@ CFG_DIR = $(BASE_DIR)/cfg
-include $(CFG_DIR)/global_vars.mk
LIBS = libtoxcore ncursesw libconfig libqrencode
LIBS = toxcore ncursesw libconfig libcurl
CFLAGS = -std=gnu99 -pthread -Wall -g -fstack-protector-all
CFLAGS ?= -std=c99 -pthread -Wall -Wpedantic -Wunused -fstack-protector-all -Wvla -Wno-missing-braces
CFLAGS += '-DTOXICVER="$(VERSION)"' -DHAVE_WIDECHAR -D_XOPEN_SOURCE_EXTENDED -D_FILE_OFFSET_BITS=64
CFLAGS += '-DPACKAGE_DATADIR="$(abspath $(DATADIR))"'
CFLAGS += $(USER_CFLAGS)
LDFLAGS = $(USER_LDFLAGS)
CFLAGS += ${USER_CFLAGS}
LDFLAGS ?=
LDFLAGS += ${USER_LDFLAGS}
OBJ = autocomplete.o avatars.o bootstrap.o chat.o chat_commands.o configdir.o curl_util.o execute.o
OBJ += file_transfers.o friendlist.o global_commands.o group_commands.o groupchat.o help.o input.o
OBJ += line_info.o log.o message_queue.o misc_tools.o name_lookup.o notify.o prompt.o qr_code.o settings.o
OBJ += file_transfers.o friendlist.o global_commands.o conference_commands.o conference.o help.o input.o line_info.o
OBJ += log.o message_queue.o misc_tools.o name_lookup.o notify.o prompt.o qr_code.o settings.o
OBJ += term_mplex.o toxic.o toxic_strings.o windows.o
# Check if debug build is enabled
RELEASE := $(shell if [ -z "$(ENABLE_RELEASE)" ] || [ "$(ENABLE_RELEASE)" = "0" ] ; then echo disabled ; else echo enabled ; fi)
ifneq ($(RELEASE), enabled)
CFLAGS += -O0 -g -DDEBUG
LDFLAGS += -O0
else
CFLAGS += -O2 -flto
LDFLAGS += -O2 -flto
endif
# Check if LLVM Address Sanitizer is enabled
ASAN := $(shell if [ -z "$(ENABLE_ASAN)" ] || [ "$(ENABLE_ASAN)" = "0" ] ; then echo disabled ; else echo enabled ; fi)
ifneq ($(ASAN), disabled)
CFLAGS += -fsanitize=address -fno-omit-frame-pointer
endif
# Check on wich system we are running
UNAME_S = $(shell uname -s)
ifeq ($(UNAME_S), Linux)
-include $(CFG_DIR)/systems/Linux.mk
endif
ifeq ($(UNAME_S), FreeBSD)
-include $(CFG_DIR)/systems/FreeBSD.mk
endif
ifeq ($(UNAME_S), DragonFly)
-include $(CFG_DIR)/systems/FreeBSD.mk
LDFLAGS += -ldl -lrt
endif
ifeq ($(UNAME_S), OpenBSD)
-include $(CFG_DIR)/systems/FreeBSD.mk
LIBS := $(filter-out ncursesw, $(LIBS))
LDFLAGS += -lncursesw
endif
ifeq ($(UNAME_S), NetBSD)
LIBS := $(filter-out ncursesw, $(LIBS))
LDFLAGS += -lncursesw
endif
ifeq ($(UNAME_S), Darwin)
-include $(CFG_DIR)/systems/Darwin.mk
endif
ifeq ($(UNAME_S), Solaris)
-include $(CFG_DIR)/systems/Solaris.mk
endif
# Check on which platform we are running
UNAME_M = $(shell uname -m)
@ -72,7 +85,7 @@ $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
fi
@echo " CC $(@:$(BUILD_DIR)/%=%)"
@$(CC) $(CFLAGS) -o $(BUILD_DIR)/$*.o -c $(SRC_DIR)/$*.c
@$(CC) -MM $(CFLAGS) $(SRC_DIR)/$*.c > $(BUILD_DIR)/$*.d
@$(CC) -MM $(CFLAGS) $(SRC_DIR)/$*.c >$(BUILD_DIR)/$*.d
clean:
rm -f $(BUILD_DIR)/*.d $(BUILD_DIR)/*.o $(BUILD_DIR)/toxic

View File

@ -3,13 +3,12 @@
src="https://scan.coverity.com/projects/4975/badge.svg"/>
</a>
Toxic is a [Tox](https://tox.chat)-based instant messenging client which formerly resided in the [Tox core repository](https://github.com/irungentoo/toxcore), and is now available as a standalone application.
Toxic is a [Tox](https://tox.chat)-based instant messaging and video chat client.
[![Toxic Screenshot](https://i.imgur.com/san99Z2.png "Home Screen")](https://i.imgur.com/san99Z2.png)
[![Toxic Screenshot](https://i.imgur.com/TwYA8L0.png "Toxic Home Screen")](https://i.imgur.com/TwYA8L0.png)
## Installation
[Use our repositories](https://wiki.tox.chat/binaries#other_linux)<br />
[Compile it yourself](/INSTALL.md)
[See the install instructions](/INSTALL.md)
## Settings
Running Toxic for the first time creates an empty file called toxic.conf in your home configuration directory ("~/.config/tox" for Linux users). Adding options to this file allows you to enable auto-logging, change the time format (12/24 hour), and much more.

20
apidoc/python/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = toxic_api
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -0,0 +1,154 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# toxic_api documentation build configuration file, created by
# sphinx-quickstart on Tue May 16 08:58:24 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'toxic_api'
copyright = '2017, Jakob Kreuze'
author = 'Jakob Kreuze'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.11.1'
# The full version, including alpha/beta/rc tags.
release = '0.11.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'toxic_apidoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'toxic_api.tex', 'toxic\\_api Documentation',
'Jakob Kreuze', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'toxic_api', 'toxic_api Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'toxic_api', 'toxic_api Documentation',
author, 'toxic_api', 'One line description of project.',
'Miscellaneous'),
]

View File

@ -0,0 +1,8 @@
============
API Examples
============
Fortune
=======
.. literalinclude:: fortune.py
:language: python

View File

@ -0,0 +1,37 @@
import toxic_api
import random
FORTUNES = [
"A bug in the code is worth two in the documentation.",
"A bug in the hand is better than one as yet undetected.",
"\"A debugged program is one for which you have not yet found the "
"conditions that make it fail.\" -- Jerry Ogdin"
]
def send_fortune(args):
"""Callback function that sends the contact of the current window a
given number of random fortunes.
"""
if len(args) != 1:
toxic_api.display("Only one argument allowed!")
return
try:
count = int(args[0])
except ValueError:
toxic_api.display("Argument must be a number!")
return
if count < 0 or count > 20:
toxic_api.display("Argument is too large!")
return
name = toxic_api.get_nick()
toxic_api.send("%s has decided to send you %d fortunes:" % (name, count))
for _ in range(count):
toxic_api.send(random.choice(FORTUNES))
toxic_api.register("/fortune", "Send a fortune to the contact of the current "
"window", send_fortune)

View File

@ -0,0 +1,9 @@
Toxic Scripting Interface Documentation
=======================================
.. toctree::
:maxdepth: 2
intro
reference
examples

View File

@ -0,0 +1,12 @@
=========================
Toxic Scripting Interface
=========================
A Python scripting interface to `Toxic <https://github.com/JFreegman/toxic>`_.
Getting Started
===============
Toxic is compiled with Python support by default. To access the scripting interface, simply import "toxic_api" in your script.
Scripts can be run by issuing "/run <path>" from toxic, or placing them in the "autorun_path" from your toxic configuration file.

View File

@ -0,0 +1,73 @@
=============
API Reference
=============
Messages
========
.. function:: display(msg)
Display a message to the user through the current window.
:param msg: The message to display.
:type msg: string
:rtype: none
.. function:: send(msg)
Send a message to the user specified by the currently open conversation.
:param msg: The message to display.
:type msg: string
:rtype: none
State
=====
.. function:: get_nick()
Return the user's current nickname.
:rtype: string
.. function:: get_status()
Return a string representing the user's current status. Can be either "online", "away", or "busy".
:rtype: string
.. function:: get_status_message()
Return the user's current status message.
:rtype: string
.. function:: get_all_friends()
Return a list of all the user's friends.
:rtype: list of (string, string) tuples containing the nickname followed by their public key
Commands
========
.. function:: execute(command, class)
Executes the given command. The API exports three constants for the class parameter; GLOBAL_COMMAND, CHAT_COMMAND, and GROUPCHAT_COMMAND.
:param command: The command to execute.
:type command: string
:param class: The class of the command.
:type class: int
:rtype: none
.. function:: register(command, help, callback)
Register a callback to be executed whenever command is run. The callback function will be called with one argument, a list of arguments from when the user calls the command.
:param command: The command to listen for.
:type command: string
:param help: A description of the command to be shown in the help menu.
:type help: string
:param callback: The function to be called.
:type callback: callable
:rtype: none

View File

@ -1,11 +1,26 @@
# Bracket Style Options
--style=kr
--pad-header
--max-code-length=120
--convert-tabs
--indent-switches
--pad-oper
--align-pointer=name
--align-reference=name
--preserve-date
--lineend=linux
--break-blocks
# Tab Options
--indent=spaces=4
# Indentation Options
--indent-switches
# Padding Options
--pad-header
--break-blocks
--pad-oper
--unpad-paren
--align-pointer=name
--align-reference=name
# Formatting Options
--add-brackets
--convert-tabs
--max-code-length=120
# Other Options
--preserve-date
--formatted
--lineend=linux

View File

@ -1,5 +1,5 @@
# Variables for audio call support
AUDIO_LIBS = libtoxav openal
AUDIO_LIBS = openal
AUDIO_CFLAGS = -DAUDIO
ifneq (, $(findstring audio_device.o, $(OBJ)))
AUDIO_OBJ = audio_call.o
@ -8,14 +8,15 @@ else
endif
# Check if we can build audio support
CHECK_AUDIO_LIBS = $(shell $(PKG_CONFIG) --exists $(AUDIO_LIBS) || echo -n "error")
CHECK_AUDIO_LIBS := $(shell $(PKG_CONFIG) --exists $(AUDIO_LIBS) || echo -n "error")
ifneq ($(CHECK_AUDIO_LIBS), error)
LIBS += $(AUDIO_LIBS)
LDFLAGS += -lm
CFLAGS += $(AUDIO_CFLAGS)
OBJ += $(AUDIO_OBJ)
else ifneq ($(MAKECMDGOALS), clean)
MISSING_AUDIO_LIBS = $(shell for lib in $(AUDIO_LIBS) ; do if ! $(PKG_CONFIG) --exists $$lib ; then echo $$lib ; fi ; done)
MISSING_AUDIO_LIBS := $(shell for lib in $(AUDIO_LIBS) ; do if ! $(PKG_CONFIG) --exists $$lib ; then echo $$lib ; fi ; done)
$(warning WARNING -- Toxic will be compiled without audio support)
$(warning WARNING -- You need these libraries for audio support)
$(warning WARNING -- $(MISSING_AUDIO_LIBS))
endif
endif

View File

@ -1,19 +1,19 @@
CHECKS_DIR = $(CFG_DIR)/checks
# Check if we want build X11 support
X11 = $(shell if [ -z "$(DISABLE_X11)" ] || [ "$(DISABLE_X11)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
X11 := $(shell if [ -z "$(DISABLE_X11)" ] || [ "$(DISABLE_X11)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
ifneq ($(X11), disabled)
-include $(CHECKS_DIR)/x11.mk
endif
# Check if we want build audio support
AUDIO = $(shell if [ -z "$(DISABLE_AV)" ] || [ "$(DISABLE_AV)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
AUDIO := $(shell if [ -z "$(DISABLE_AV)" ] || [ "$(DISABLE_AV)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
ifneq ($(AUDIO), disabled)
-include $(CHECKS_DIR)/audio.mk
endif
# Check if we want build video support
VIDEO = $(shell if [ -z "$(DISABLE_AV)" ] || [ "$(DISABLE_AV)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
VIDEO := $(shell if [ -z "$(DISABLE_VI)" ] || [ "$(DISABLE_VI)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
ifneq ($(X11), disabled)
ifneq ($(AUDIO), disabled)
ifneq ($(VIDEO), disabled)
@ -22,31 +22,49 @@ endif
endif
endif
#check if we want to build with game support
GAMES := $(shell if [ -z "$(DISABLE_GAMES)" ] || [ "$(DISABLE_GAMES)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
ifneq ($(GAMES), disabled)
-include $(CHECKS_DIR)/games.mk
endif
# Check if we want build sound notifications support
SND_NOTIFY = $(shell if [ -z "$(DISABLE_SOUND_NOTIFY)" ] || [ "$(DISABLE_SOUND_NOTIFY)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
SND_NOTIFY := $(shell if [ -z "$(DISABLE_SOUND_NOTIFY)" ] || [ "$(DISABLE_SOUND_NOTIFY)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
ifneq ($(SND_NOTIFY), disabled)
-include $(CHECKS_DIR)/sound_notifications.mk
endif
# Check if we want build desktop notifications support
DESK_NOTIFY = $(shell if [ -z "$(DISABLE_DESKTOP_NOTIFY)" ] || [ "$(DISABLE_DESKTOP_NOTIFY)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
DESK_NOTIFY := $(shell if [ -z "$(DISABLE_DESKTOP_NOTIFY)" ] || [ "$(DISABLE_DESKTOP_NOTIFY)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
ifneq ($(DESK_NOTIFY), disabled)
-include $(CHECKS_DIR)/desktop_notifications.mk
endif
# Check if we want build QR export support
QR_CODE := $(shell if [ -z "$(DISABLE_QRCODE)" ] || [ "$(DISABLE_QRCODE)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
ifneq ($(QR_CODE), disabled)
-include $(CHECKS_DIR)/qr.mk
endif
# Check if we want build QR exported as PNG support
QR_PNG = $(shell if [ -z "$(DISABLE_QRPNG)" ] || [ "$(DISABLE_QRPNG)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
QR_PNG := $(shell if [ -z "$(DISABLE_QRPNG)" ] || [ "$(DISABLE_QRPNG)" = "0" ] ; then echo enabled ; else echo disabled ; fi)
ifneq ($(QR_PNG), disabled)
-include $(CHECKS_DIR)/qr_png.mk
endif
# Check if we want build Python scripting support
PYTHON := $(shell if [ -z "$(ENABLE_PYTHON)" ] || [ "$(ENABLE_PYTHON)" = "0" ] ; then echo disabled ; else echo enabled ; fi)
ifneq ($(PYTHON), disabled)
-include $(CHECKS_DIR)/python.mk
endif
# Check if we can build Toxic
CHECK_LIBS = $(shell $(PKG_CONFIG) --exists $(LIBS) || echo -n "error")
CHECK_LIBS := $(shell $(PKG_CONFIG) --exists $(LIBS) || echo -n "error")
ifneq ($(CHECK_LIBS), error)
CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBS))
LDFLAGS += $(shell $(PKG_CONFIG) --libs $(LIBS))
else ifneq ($(MAKECMDGOALS), clean)
MISSING_LIBS = $(shell for lib in $(LIBS) ; do if ! $(PKG_CONFIG) --exists $$lib ; then echo $$lib ; fi ; done)
MISSING_LIBS := $(shell for lib in $(LIBS) ; do if ! $(PKG_CONFIG) --exists $$lib ; then echo $$lib ; fi ; done)
$(warning ERROR -- Cannot compile Toxic)
$(warning ERROR -- You need these libraries)
$(warning ERROR -- $(MISSING_LIBS))

View File

@ -3,12 +3,12 @@ DESK_NOTIFY_LIBS = libnotify
DESK_NOTIFY_CFLAGS = -DBOX_NOTIFY
# Check if we can build desktop notifications support
CHECK_DESK_NOTIFY_LIBS = $(shell $(PKG_CONFIG) --exists $(DESK_NOTIFY_LIBS) || echo -n "error")
CHECK_DESK_NOTIFY_LIBS := $(shell $(PKG_CONFIG) --exists $(DESK_NOTIFY_LIBS) || echo -n "error")
ifneq ($(CHECK_DESK_NOTIFY_LIBS), error)
LIBS += $(DESK_NOTIFY_LIBS)
CFLAGS += $(DESK_NOTIFY_CFLAGS)
else ifneq ($(MAKECMDGOALS), clean)
MISSING_DESK_NOTIFY_LIBS = $(shell for lib in $(DESK_NOTIFY_LIBS) ; do if ! $(PKG_CONFIG) --exists $$lib ; then echo $$lib ; fi ; done)
MISSING_DESK_NOTIFY_LIBS := $(shell for lib in $(DESK_NOTIFY_LIBS) ; do if ! $(PKG_CONFIG) --exists $$lib ; then echo $$lib ; fi ; done)
$(warning WARNING -- Toxic will be compiled without desktop notifications support)
$(warning WARNING -- You need these libraries for desktop notifications support)
$(warning WARNING -- $(MISSING_DESK_NOTIFY_LIBS))

5
cfg/checks/games.mk Normal file
View File

@ -0,0 +1,5 @@
# Variables for game support
GAMES_CFLAGS = -DGAMES
GAMES_OBJ = game_base.o game_centipede.o game_chess.o game_life.o game_util.o game_snake.o
CFLAGS += $(GAMES_CFLAGS)
OBJ += $(GAMES_OBJ)

15
cfg/checks/python.mk Normal file
View File

@ -0,0 +1,15 @@
# Variables for Python scripting support
PYTHON3_LIBS = python3
PYTHON_CFLAGS = -DPYTHON
PYTHON_OBJ = api.o python_api.o
# Check if we can build Python scripting support
CHECK_PYTHON3_LIBS = $(shell $(PKG_CONFIG) --exists $(PYTHON3_LIBS) || echo -n "error")
ifneq ($(CHECK_PYTHON3_LIBS), error)
LDFLAGS += $(shell python3-config --ldflags)
CFLAGS += $(PYTHON_CFLAGS) $(shell python3-config --includes)
OBJ += $(PYTHON_OBJ)
else ifneq ($(MAKECMDGOALS), clean)
$(warning WARNING -- Toxic will be compiled without Python scripting support)
$(warning WARNING -- You need python3 installed for Python scripting support)
endif

15
cfg/checks/qr.mk Normal file
View File

@ -0,0 +1,15 @@
# Variables for QR export support
QR_LIBS = libqrencode
QR_CFLAGS = -DQRCODE
# Check if we can build QR export support
CHECK_QR_LIBS = $(shell pkg-config --exists $(QR_LIBS) || echo -n "error")
ifneq ($(CHECK_QR_LIBS), error)
LIBS += $(QR_LIBS)
CFLAGS += $(QR_CFLAGS)
else ifneq ($(MAKECMDGOALS), clean)
MISSING_QR_LIBS = $(shell for lib in $(QR_LIBS) ; do if ! $(PKG_CONFIG) --exists $$lib ; then echo $$lib ; fi ; done)
$(warning WARNING -- Toxic will be compiled without QR export support)
$(warning WARNING -- You need these libraries for QR export support)
$(warning WARNING -- $(MISSING_QR_LIBS))
endif

View File

@ -1,5 +1,5 @@
# Variables for video call support
VIDEO_LIBS = libtoxav vpx x11
VIDEO_LIBS = openal vpx x11
VIDEO_CFLAGS = -DVIDEO
ifneq (, $(findstring video_device.o, $(OBJ)))
VIDEO_OBJ = video_call.o

View File

@ -1,7 +1,7 @@
# Variables for X11 support
X11_LIBS = x11
X11_CFLAGS = -DX11
X11_OBJ = xtra.o
X11_OBJ = x11focus.o
# Check if we can build X11 support
CHECK_X11_LIBS = $(shell $(PKG_CONFIG) --exists $(X11_LIBS) || echo -n "error")

View File

@ -1,5 +1,5 @@
# Version
TOXIC_VERSION = 0.7.1
TOXIC_VERSION = 0.11.1
REV = $(shell git rev-list HEAD --count 2>/dev/null || echo -n "error")
ifneq (, $(findstring error, $(REV)))
VERSION = $(TOXIC_VERSION)
@ -23,10 +23,10 @@ SNDFILES += ToxicRecvMessage.wav ToxicOutgoingCall.wav ToxicIncomingCall.wav
SNDFILES += ToxicTransferComplete.wav ToxicTransferStart.wav
# Install directories
PREFIX = /usr/local
PREFIX ?= /usr/local
BINDIR = $(PREFIX)/bin
DATADIR = $(PREFIX)/share/toxic
MANDIR = $(PREFIX)/share/man
MANDIR ?= $(PREFIX)/share/man
APPDIR = $(PREFIX)/share/applications
# Platform tools

View File

@ -6,7 +6,7 @@ PKG_CONFIG_PATH = $(shell export PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/local/o
LIBS := $(filter-out ncursesw, $(LIBS))
# OS X ships a usable, recent version of ncurses, but calls it ncurses not ncursesw.
LDFLAGS += -lncurses -lalut -ltoxav -ltoxcore -lcurl -lconfig -ltoxencryptsave -g
LDFLAGS += -lncurses -lalut -ltoxcore -lcurl -lconfig -lqrencode -lpng -lopenal -g
CFLAGS += -I/usr/local/opt/freealut/include/AL -I/usr/local/opt/glib/include/glib-2.0 -g
OSX_LIBRARIES = -lobjc -lresolv

View File

@ -1,4 +0,0 @@
# Specials options for freebsd systems
LIBS := $(filter-out ncursesw, $(LIBS))
LDFLAGS += -lncursesw -lcurl
MANDIR = $(PREFIX)/man

View File

@ -1,4 +0,0 @@
# Specials options for linux systems
CFLAGS +=
LDFLAGS += -ldl -lrt -lcurl
MANDIR = $(PREFIX)/share/man

View File

@ -1,10 +1,10 @@
# Doc target
doc: $(MANFILES:%=$(DOC_DIR)/%)
$(DOC_DIR)/%: $(DOC_DIR)/%.asc
@echo " MAN $(@F)"
@a2x -f manpage -a revdate=$(shell git log -1 --date=short --format="%ad" $<) \
-a manmanual="Toxic Manual" -a mansource=toxic \
-a manversion=__VERSION__ -a datadir=__DATADIR__ $<
.PHONY: doc

View File

@ -14,10 +14,25 @@ help:
@echo " DISABLE_AV: Set to \"1\" to force building without audio call support"
@echo " DISABLE_SOUND_NOTIFY: Set to \"1\" to force building without sound notification support"
@echo " DISABLE_DESKTOP_NOTIFY: Set to \"1\" to force building without desktop notifications support"
@echo " DISABLE_QRCODE: Set to \"1\" to force building without QR export support"
@echo " DISABLE_QRPNG: Set to \"1\" to force building without QR exported as PNG support"
@echo " DISABLE_GAMES: Set to \"1\" to force building without game support"
@echo " ENABLE_PYTHON: Set to \"1\" to enable building with Python scripting support"
@echo " ENABLE_RELEASE: Set to \"1\" to build without debug symbols and with full compiler optimizations"
@echo " ENABLE_ASAN: Set to \"1\" to build with LLVM address sanitizer enabled.
@echo " USER_CFLAGS: Add custom flags to default CFLAGS"
@echo " USER_LDFLAGS: Add custom flags to default LDFLAGS"
@echo " PREFIX: Specify a prefix directory for binaries, data files,... (default is \"$(abspath $(PREFIX))\")"
@echo " DESTDIR: Specify a directory where to store installed files (mainly for packaging purpose)"
@echo " MANDIR: Specify a directory where to store man pages (default is \"$(abspath $(PREFIX)/share/man)\")"
@echo
@echo "-- Environment Variables --"
@echo " CFLAGS: Add custom flags to default CFLAGS"
@echo " LDFLAGS: Add custom flags to default LDFLAGS"
@echo " USER_CFLAGS: Add custom flags to default CFLAGS"
@echo " USER_LDFLAGS: Add custom flags to default LDFLAGS"
@echo " PREFIX: Specify a prefix directory for binaries, data files,... (default is \"$(abspath $(PREFIX))\")"
@echo " DESTDIR: Specify a directory where to store installed files (mainly for packaging purpose)"
@echo " MANDIR: Specify a directory where to store man pages (default is \"$(abspath $(PREFIX)/share/man)\")"
.PHONY: help

View File

@ -27,7 +27,7 @@ install: $(BUILD_DIR)/toxic
if [ ! -e "$(DOC_DIR)/$$f" ]; then \
continue ;\
fi ;\
section=$(abspath $(DESTDIR)/$(MANDIR))/man`echo $$f | rev | cut -d "." -f 1` ;\
section=$(abspath $(DESTDIR)/$(MANDIR))/man`echo $${f##*.}` ;\
file=$$section/$$f ;\
mkdir -p $$section ;\
install -m 0644 $(DOC_DIR)/$$f $$file ;\
@ -35,7 +35,7 @@ install: $(BUILD_DIR)/toxic
mv temp_file $$file ;\
sed -e 's:__DATADIR__:'$(abspath $(DATADIR))':g' $$file > temp_file && \
mv temp_file $$file ;\
gzip -f -9 $$file ;\
gzip -f -n -9 $$file ;\
done
.PHONY: install

View File

@ -16,7 +16,7 @@ uninstall:
@echo "Removing man pages"
@for f in $(MANFILES) ; do \
section=$(abspath $(DESTDIR)/$(MANDIR))/man`echo $$f | rev | cut -d "." -f 1` ;\
section=$(abspath $(DESTDIR)/$(MANDIR))/man`echo $${f##*.}` ;\
file=$$section/$$f ;\
rm -f $$file $$file.gz ;\
done

View File

@ -1,13 +1,13 @@
'\" t
.\" Title: toxic
.\" Author: [see the "AUTHORS" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
.\" Date: 2016-09-20
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
.\" Date: 2020-05-04
.\" Manual: Toxic Manual
.\" Source: toxic __VERSION__
.\" Language: English
.\"
.TH "TOXIC" "1" "2016\-09\-20" "toxic __VERSION__" "Toxic Manual"
.TH "TOXIC" "1" "2020\-05\-04" "toxic __VERSION__" "Toxic Manual"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@ -78,6 +78,11 @@ instead of
Show help message
.RE
.PP
\-l, \-\-logging
.RS 4
Enable toxcore logging to stderr
.RE
.PP
\-n, \-\-nodes nodes\-file
.RS 4
Use specified
@ -161,4 +166,4 @@ JFreegman <JFreegman@gmail\&.com>
.sp
Project page: https://github\&.com/JFreegman/toxic
.sp
IRC channel: chat\&.freenode\&.net#tox
IRC channel: irc\&.libera\&.chat#tox

View File

@ -40,6 +40,9 @@ OPTIONS
-h, --help::
Show help message
-l, --logging::
Enable toxcore logging to stderr
-n, --nodes nodes-file::
Use specified 'nodes-file' for DHT bootstrap nodes instead of '~/.config/tox/DHTnodes.json'
@ -102,4 +105,4 @@ LINKS
-----
Project page: <https://github.com/JFreegman/toxic>
IRC channel: chat.freenode.net#tox
IRC channel: irc.libera.chat#tox

View File

@ -1,13 +1,13 @@
'\" t
.\" Title: toxic.conf
.\" Author: [see the "AUTHORS" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
.\" Date: 2016-07-21
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
.\" Date: 2020-12-05
.\" Manual: Toxic Manual
.\" Source: toxic __VERSION__
.\" Language: English
.\"
.TH "TOXIC\&.CONF" "5" "2016\-07\-21" "toxic __VERSION__" "Toxic Manual"
.TH "TOXIC\&.CONF" "5" "2020\-12\-05" "toxic __VERSION__" "Toxic Manual"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@ -93,6 +93,26 @@ Enable or disable acoustic alerts on events\&. true or false
Select between native terminal colors and toxic color theme\&. true or false
.RE
.PP
\fBcolor_bar_bg\fR
.RS 4
set background color of chat status bars\&. (black, white, red, green, blue, cyan, yellow, magenta)
.RE
.PP
\fBcolor_bar_fg\fR
.RS 4
set foreground (text) color of chat status bars\&. (black, white, red, green, blue, cyan, yellow, magenta)
.RE
.PP
\fBcolor_bar_accent\fR
.RS 4
set foreground accent color of chat status bars\&. (black, white, red, green, blue, cyan, yellow, magenta)
.RE
.PP
\fBcolor_bar_notify\fR
.RS 4
set foreground notify (and typing) color in chat status bar\&. (black, white, red, green, blue, cyan, yellow, magenta)
.RE
.PP
\fBautolog\fR
.RS 4
Enable or disable autologging\&. true or false
@ -120,7 +140,12 @@ Enable friend connection change notifications\&. true or false
.PP
\fBnodelist_update_freq\fR
.RS 4
How often in days to update the DHT nodes list\&. (0 to disable updates)
How often in days to update the DHT nodes list\&. (integer; 0 to disable)
.RE
.PP
\fBautosave_freq\fR
.RS 4
How often in seconds to auto\-save the Tox data file\&. (integer; 0 to disable)
.RE
.PP
\fBhistory_size\fR
@ -128,6 +153,11 @@ How often in days to update the DHT nodes list\&. (0 to disable updates)
Maximum lines for chat window history\&. Integer value\&. (for example: 700)
.RE
.PP
\fBnotification_timeout\fR
.RS 4
Time in milliseconds to display a notification\&. Integer value\&. (for example: 3000)
.RE
.PP
\fBline_join\fR
.RS 4
Indicator for when someone connects or joins a group\&. Three characters max for line_ settings\&.
@ -207,9 +237,24 @@ Audio output device\&. Integer value\&. Number corresponds to
/lsdev out
.RE
.PP
\fBVAD_treshold\fR
\fBVAD_threshold\fR
.RS 4
Voice Activity Detection treshold\&. Float value\&. Recommended values are around 40\&.0
Voice Activity Detection threshold\&. Float value\&. Recommended values are 1\&.0\-40\&.0
.RE
.PP
\fBconference_audio_channels\fR
.RS 4
Number of channels for conference audio broadcast\&. Integer value\&. 1 (mono) or 2 (stereo)
.RE
.PP
\fBchat_audio_channels\fR
.RS 4
Number of channels for 1\-on\-1 audio broadcast\&. Integer value\&. 1 (mono) or 2 (stereo)
.RE
.PP
\fBpush_to_talk\fR
.RS 4
Enable/Disable Push\-To\-Talk for conference audio chats (active key is F2)\&. true or false
.RE
.RE
.PP
@ -227,6 +272,11 @@ Default path for downloads\&. String value\&. Absolute path for downloaded files
Path for your avatar (file must be a \&.png and cannot exceed 16\&.3 KiB)
.RE
.PP
\fBautorun_path\fR
.RS 4
Path for any scripts that should be run on startup
.RE
.PP
\fBchatlogs_path\fR
.RS 4
Default path for chatlogs\&. String value\&. Absolute path for chatlog files\&.
@ -336,16 +386,6 @@ Key combination to scroll half page down\&.
Key combination to scroll to page bottom\&.
.RE
.PP
\fBpeer_list_up\fR
.RS 4
Key combination to scroll contacts list up\&.
.RE
.PP
\fBpeer_list_down\fR
.RS 4
Key combination to scroll contacts list down\&.
.RE
.PP
\fBtoggle_peerlist\fR
.RS 4
Toggle the peer list on and off\&.
@ -374,7 +414,7 @@ Configuration example\&.
.sp
Project page: https://github\&.com/JFreegman/toxic
.sp
IRC channel: chat\&.freenode\&.net#tox
IRC channel: irc\&.libera\&.chat#tox
.SH "AUTHORS"
.sp
JFreegman <JFreegman@gmail\&.com>

View File

@ -60,6 +60,18 @@ OPTIONS
*native_colors*;;
Select between native terminal colors and toxic color theme. true or false
*color_bar_bg*;;
set background color of chat status bars. (black, white, red, green, blue, cyan, yellow, magenta)
*color_bar_fg*;;
set foreground (text) color of chat status bars. (black, white, red, green, blue, cyan, yellow, magenta)
*color_bar_accent*;;
set foreground accent color of chat status bars. (black, white, red, green, blue, cyan, yellow, magenta)
*color_bar_notify*;;
set foreground notify (and typing) color in chat status bar. (black, white, red, green, blue, cyan, yellow, magenta)
*autolog*;;
Enable or disable autologging. true or false
@ -76,11 +88,17 @@ OPTIONS
Enable friend connection change notifications. true or false
*nodelist_update_freq*;;
How often in days to update the DHT nodes list. (0 to disable updates)
How often in days to update the DHT nodes list. (integer; 0 to disable)
*autosave_freq*;;
How often in seconds to auto-save the Tox data file. (integer; 0 to disable)
*history_size*;;
Maximum lines for chat window history. Integer value. (for example: 700)
*notification_timeout*;;
Time in milliseconds to display a notification. Integer value. (for example: 3000)
*line_join*;;
Indicator for when someone connects or joins a group.
Three characters max for line_ settings.
@ -129,9 +147,18 @@ OPTIONS
*output_device*;;
Audio output device. Integer value. Number corresponds to `/lsdev out`
*VAD_treshold*;;
Voice Activity Detection treshold. Float value. Recommended values are
around 40.0
*VAD_threshold*;;
Voice Activity Detection threshold. Float value. Recommended values are
1.0-40.0
*conference_audio_channels*;;
Number of channels for conference audio broadcast. Integer value. 1 (mono) or 2 (stereo)
*chat_audio_channels*;;
Number of channels for 1-on-1 audio broadcast. Integer value. 1 (mono) or 2 (stereo)
*push_to_talk*;;
Enable/Disable Push-To-Talk for conference audio chats (active key is F2). true or false
*tox*::
Configuration related to paths.
@ -143,6 +170,9 @@ OPTIONS
*avatar_path*;;
Path for your avatar (file must be a .png and cannot exceed 16.3 KiB)
*autorun_path*;;
Path for any scripts that should be run on startup
*chatlogs_path*;;
Default path for chatlogs. String value. Absolute path for chatlog files.
@ -212,12 +242,6 @@ OPTIONS
*page_bottom*;;
Key combination to scroll to page bottom.
*peer_list_up*;;
Key combination to scroll contacts list up.
*peer_list_down*;;
Key combination to scroll contacts list down.
*toggle_peerlist*;;
Toggle the peer list on and off.
@ -243,7 +267,7 @@ RESOURCES
---------
Project page: <https://github.com/JFreegman/toxic>
IRC channel: chat.freenode.net#tox
IRC channel: irc.libera.chat#tox
AUTHORS

View File

@ -9,20 +9,32 @@ ui = {
alerts=true;
// Output a bell when receiving a message (see manpage)
bell_on_message=true
bell_on_message=true;
// Output a bell when receiving a filetransfer (see manpage)
bell_on_filetrans=true
bell_on_filetrans=true;
// Don't output a bell when a filetransfer was accepted (see manpage)
bell_on_filetrans_accept=false
bell_on_filetrans_accept=false;
// Output a bell when receiving a group/call invite (see manpage)
bell_on_invite=true
bell_on_invite=true;
// true to use native terminal colours, false to use toxic default colour theme
native_colors=false;
// set background color of chat status bars (black, white, red, green, blue, cyan, yellow, magenta)
color_bar_bg="blue";
// set foreground (text) color of chat status bars (black, white, red, green, blue, cyan, yellow, magenta)
color_bar_fg="white";
// set foreground accent color of chat status bars (black, white, red, green, blue, cyan, yellow, magenta)
color_bar_accent="cyan";
// set foreground notify (and typing) color in chat status bar (black, white, red, green, blue, cyan, yellow, magenta)
color_bar_notify="yellow";
// true to enable autologging, false to disable
autolog=false;
@ -30,7 +42,7 @@ ui = {
time_format=24;
// Timestamp format string according to date/strftime format. Overrides time_format setting
timestamp_format="%H:%M:%S";
timestamp_format="%H:%M";
// true to show you when others are typing a message in 1-on-1 chats
show_typing_other=true;
@ -47,20 +59,26 @@ ui = {
// How often in days to update the DHT nodes list. (0 to disable updates)
nodeslist_update_freq=7;
// How often in seconds to auto-save the Tox data file. (0 to disable periodic auto-saves)
autosave_freq=600;
// maximum lines for chat window history
history_size=700;
// Indicator for display when someone connects or joins a group.
// time in milliseconds to display a notification
notification_timeout=6000;
// Indicator for display when someone connects or joins a group
line_join="-->";
// Indicator for display when someone disconnects or leaves a group.
// Indicator for display when someone disconnects or leaves a group
line_quit="<--";
// Indicator for alert messages.
line_alert="-!-";
// Indicator for normal messages.
line_normal="---";
line_normal="-";
// true to change status based on screen/tmux attach/detach, false to disable
mplex_away=true;
@ -76,8 +94,17 @@ audio = {
// preferred audio output device; numbers correspond to /lsdev out
output_device=0;
// default VAD treshold; float (recommended values are around 40)
VAD_treshold=40.0;
// default VAD threshold; float (recommended values are 1.0-40.0)
VAD_threshold=5.0;
// Number of channels to use for conference audio broadcasts; 1 for mono, 2 for stereo.
conference_audio_channels=1;
// Number of channels to use for 1-on-1 audio broadcasts; 1 for mono, 2 for stereo.
chat_audio_channels=2;
// toggle conference push-to-talk
push_to_talk=false;
};
tox = {
@ -87,6 +114,9 @@ tox = {
// Path for your avatar (file must be a .png and cannot exceed 64 KiB)
// avatar_path="/home/USERNAME/Pictures/youravatar.png";
// Path for scripts that should be run on startup
// autorun_path="/home/USERNAME/toxic_scripts/";
// Path for chatlogs
// chatlogs_path="/home/USERNAME/toxic_chatlogs/";
};
@ -113,9 +143,6 @@ keys = {
half_page_up="Ctrl+F";
half_page_down="Ctrl+V";
page_bottom="Ctrl+H";
peer_list_up="Ctrl+[";
peer_list_down="Ctrl+]";
toggle_peerlist="Ctrl+b";
toggle_peerlist="Ctrl+B";
toggle_paste_mode="Ctrl+T";
};

View File

@ -0,0 +1,304 @@
#!/usr/bin/env sh
# MIT License
#
# Copyright (c) 2021 Maxim Biro <nurupo.contributions@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Script for building a minimal statically compiled Toxic. While it doesn't
# support X11 integration, video/audio calls, desktop & sound notifications, QR
# codes and Python scripting, it is rather portable.
#
# Run as:
#
# sudo docker run -it --rm \
# -v /tmp/artifact:/artifact \
# -v /home/jfreegman/git/toxic:/toxic \
# amd64/alpine:latest \
# /bin/sh /toxic/script/build-minimal-static-toxic.sh
#
# that would use Toxic code from /home/jfreegman/git/toxic and place the build
# artifact at /tmp/artifact.
# You can change between amd64/alpine:latest and i386/alpine:latest, for 64-bit
# and 32-bit builds.
#
# To debug, run:
#
# sudo docker run -it --rm \
# -v /tmp/artifact:/artifact \
# -v /home/jfreegman/git/toxic:/toxic \
# amd64/alpine:latest \
# /bin/sh
# # sh /toxic/script/build-minimal-static-toxic.sh
set -eu
ARTIFACT_DIR="/artifact"
TOXIC_SRC_DIR="/toxic"
# Make sure we run in the expected environment
if ! grep -q 'docker' /proc/1/cgroup
then
echo "Error: This script should be run inside a disposable Docker container as it might modify system files in ways that would break a real system."
exit 1
fi
if [ ! -f /etc/os-release ] || ! grep -qi 'Alpine Linux' /etc/os-release
then
echo "Error: This script expects to be run on Alpine Linux."
exit 1
fi
if [ ! -d "$ARTIFACT_DIR" ] || [ ! -d "$TOXIC_SRC_DIR" ]
then
echo "Error: At least one of $ARTIFACT_DIR or $TOXIC_SRC_DIR directories inside the container is missing."
exit 1
fi
if [ "$(id -u)" != "0" ]
then
echo "Error: This script expects to be run as root."
exit 1
fi
set -x
# Use all cores for building
MAKEFLAGS=j$(nproc)
export MAKEFLAGS
check_sha256()
{
if ! ( echo "$1 $2" | sha256sum -cs - )
then
echo "Error: sha256 of $2 doesn't match the known one."
echo "Expected: $1 $2"
echo "Got: $(sha256sum "$2")"
exit 1
else
echo "sha256 matches the expected one: $1"
fi
}
apk update
apk upgrade
apk add \
brotli-dev \
brotli-static \
build-base \
cmake \
git \
libconfig-dev \
libconfig-static \
libsodium-dev \
libsodium-static \
linux-headers \
ncurses-dev \
ncurses-static \
ncurses-terminfo \
ncurses-terminfo-base \
nghttp2-dev \
nghttp2-static \
openssl-dev \
openssl-libs-static \
pkgconf \
wget \
xz \
zlib-dev \
zlib-static
BUILD_DIR="/tmp/build"
mkdir -p "$BUILD_DIR"
# Build Toxcore
cd "$BUILD_DIR"
# The git hash of the c-toxcore version we're using
TOXCORE_VERSION="25a56c354937e9c8c4c50a64c3b4cfc099c34e29"
# The sha256sum of the c-toxcore tarball for TOXCORE_VERSION
TOXCORE_HASH="8448752e6286c747130254571fde2db8e2fc073a8116f9fff489ed53af546c0a"
TOXCORE_FILENAME="c-toxcore-$TOXCORE_VERSION.tar.gz"
wget --timeout=10 -O "$TOXCORE_FILENAME" "https://github.com/TokTok/c-toxcore/archive/$TOXCORE_VERSION.tar.gz"
check_sha256 "$TOXCORE_HASH" "$TOXCORE_FILENAME"
tar -o -xf "$TOXCORE_FILENAME"
rm "$TOXCORE_FILENAME"
cd c-toxcore*
cmake -B_build -H. \
-DENABLE_STATIC=ON \
-DENABLE_SHARED=OFF \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TOXAV=OFF \
-DBOOTSTRAP_DAEMON=OFF \
-DDHT_BOOTSTRAP=OFF \
-DCMAKE_INSTALL_PREFIX="$BUILD_DIR/prefix-toxcore"
cmake --build _build --target install
# Build cURL
# While Alpine does provide a static cURL build, it's not built with
# --with-ca-fallback, which is needed for better cross-distro portability.
# Basically, some distros put their ca-certificates in different places, and
# with --with-ca-fallback we or the user can provide the cert bundle file
# location with SSL_CERT_FILE env variable.
cd "$BUILD_DIR"
CURL_VERSION="7.77.0"
CURL_HASH="b0a3428acb60fa59044c4d0baae4e4fc09ae9af1d8a3aa84b2e3fbcd99841f77"
CURL_FILENAME="curl-$CURL_VERSION.tar.gz"
wget --timeout=10 -O "$CURL_FILENAME" "https://curl.haxx.se/download/$CURL_FILENAME"
check_sha256 "$CURL_HASH" "$CURL_FILENAME"
tar -xf curl*.tar.gz
rm curl*.tar.gz
cd curl*
./configure \
--prefix="$BUILD_DIR/prefix-curl" \
--disable-shared \
--enable-static \
--without-ca-bundle \
--without-ca-path \
--with-ca-fallback \
--with-nghttp2 \
--with-brotli \
--with-openssl
make
make install
sed -i 's|-lbrotlidec |-lbrotlidec-static -lbrotlicommon-static |g' $BUILD_DIR/prefix-curl/lib/pkgconfig/libcurl.pc
# Build Toxic
cd "$BUILD_DIR"
cp -a "$TOXIC_SRC_DIR" toxic
cd toxic
if [ -z "$(git describe --tags --exact-match HEAD)" ]
then
set +x
echo "Didn't find a git tag on the HEAD commit. You seem to be building an in-development release of Toxic rather than a release version." | fold -sw 80
printf "Do you wish to proceed? (y/N): "
read -r answer
if echo "$answer" | grep -v -iq "^y" ; then
echo "Exiting."
exit 1
fi
set -x
fi
sed -i 's|pkg-config|pkg-config --static|' cfg/global_vars.mk
sed -i 's|<limits.h|<linux/limits.h|' src/*
CFLAGS="-static" PKG_CONFIG_PATH="$BUILD_DIR/prefix-toxcore/lib64/pkgconfig:$BUILD_DIR/prefix-toxcore/lib/pkgconfig:$BUILD_DIR/prefix-curl/lib/pkgconfig" PREFIX="$BUILD_DIR/prefix-toxic" make \
DISABLE_X11=1 \
DISABLE_AV=1 \
DISABLE_SOUND_NOTIFY=1 \
DISABLE_QRCODE=1 \
DISABLE_QRPNG=1 \
DISABLE_DESKTOP_NOTIFY=1 \
ENABLE_PYTHON=0 \
ENABLE_RELEASE=1 \
ENABLE_ASAN=0 \
DISABLE_GAMES=0 \
install
# Prepare the build artifact
PREPARE_ARTIFACT_DIR="$BUILD_DIR/artifact"
cp -a "$BUILD_DIR/prefix-toxic/bin" "$PREPARE_ARTIFACT_DIR"
strip "$PREPARE_ARTIFACT_DIR"/*
cp -a "$BUILD_DIR/toxic/misc"/* "$PREPARE_ARTIFACT_DIR"
mv "$PREPARE_ARTIFACT_DIR/toxic.conf.example" "$PREPARE_ARTIFACT_DIR/toxic.conf"
cp -aL /usr/share/terminfo "$PREPARE_ARTIFACT_DIR"
echo "A minimal statically compiled Toxic.
Doesn't support X11 integration, video/audio calls, desktop & sound
notifications, QR codes and Python scripting.
However, it is rather portable.
Toxic $(git -C "$BUILD_DIR/toxic" describe --tags --exact-match HEAD) ($(git -C "$BUILD_DIR/toxic" rev-parse HEAD))
Build date time: $(TZ=UTC date +"%Y-%m-%dT%H:%M:%S%z")
OS:
$(cat /etc/os-release)
List of self-built software statically compiled into Toxic:
libcurl $CURL_VERSION
libtoxcore $TOXCORE_VERSION
List of OS-packaged software statically compiled into Toxic:
$(apk list -I | grep 'static' | sort -i)
List of all packages installed during the build:
$(apk list -I | sort -i)" > "$PREPARE_ARTIFACT_DIR/build_info"
echo '#!/usr/bin/env sh
DEBIAN_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
RHEL_SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt
OPENSUSE_CERT_FILE=/etc/ssl/ca-bundle.pem
if [ ! -f "$SSL_CERT_FILE" ] ; then
if [ -f "$DEBIAN_SSL_CERT_FILE" ] ; then
SSL_CERT_FILE="$DEBIAN_SSL_CERT_FILE"
elif [ -f "$RHEL_SSL_CERT_FILE" ] ; then
SSL_CERT_FILE="$RHEL_SSL_CERT_FILE"
elif [ -f "$OPENSUSE_CERT_FILE" ] ; then
SSL_CERT_FILE="$OPENSUSE_CERT_FILE"
fi
fi
if [ -z "$SSL_CERT_FILE" ] ; then
echo "Warning: Couldn'\''t find the SSL CA certificate store file." | fold -sw 80
echo
echo "Toxic uses HTTPS to download a list of DHT bootstrap nodes in order to connect to the Tox DHT. This functionality is optional, you should be able to use Toxic without it. If you choose to use Toxic without it, you might need to manually enter DHT bootstrap node information using the '\''/connect'\'' command in order to come online." | fold -sw 80
echo
echo "To fix this issue, install SSL CAs as provided by your Linux distribution, e.g. '\''ca-certificates'\'' package on Debian/Ubuntu. If it'\''s already installed and you still see this message, run this script with SSL_CERT_FILE variable set to point to the SSL CA certificate store file location. The file is usually named '\''ca-certificates.crt'\'' or '\''ca-bundle.pem'\''." | fold -sw 80
echo
printf "Do you wish to run Toxic without SSL CA certificate store file found? (y/N): "
read -r answer
if echo "$answer" | grep -v -iq "^y" ; then
echo "Exiting."
exit
fi
fi
cd "$(dirname -- $0)"
SSL_CERT_FILE="$SSL_CERT_FILE" TERMINFO=./terminfo ./toxic -c toxic.conf' > "$PREPARE_ARTIFACT_DIR/run_toxic.sh"
chmod a+x "$PREPARE_ARTIFACT_DIR/run_toxic.sh"
# Tar it
cd "$PREPARE_ARTIFACT_DIR"
cd ..
ARCH="$(tr '_' '-' < /etc/apk/arch)"
ARTIFACT_NAME="toxic-minimal-static-musl_linux_$ARCH"
mv "$PREPARE_ARTIFACT_DIR" "$PREPARE_ARTIFACT_DIR/../$ARTIFACT_NAME"
tar -cJf "$ARTIFACT_NAME.tar.xz" "$ARTIFACT_NAME"
mv "$ARTIFACT_NAME.tar.xz" "$ARTIFACT_DIR"
chmod 777 -R "$ARTIFACT_DIR"

217
src/api.c Normal file
View File

@ -0,0 +1,217 @@
/* api.c
*
*
* Copyright (C) 2017 Jakob Kreuze <jakob@memeware.net>
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <dirent.h>
#include <stdint.h>
#include <tox/tox.h>
#include "execute.h"
#include "friendlist.h"
#include "line_info.h"
#include "message_queue.h"
#include "misc_tools.h"
#include "settings.h"
#include "toxic_strings.h"
#include "windows.h"
#ifdef PYTHON
#include "python_api.h"
Tox *user_tox;
static WINDOW *cur_window;
static ToxWindow *self_window;
extern FriendsList Friends;
extern struct user_settings *user_settings;
void api_display(const char *const msg)
{
if (msg == NULL) {
return;
}
self_window = get_active_window();
line_info_add(self_window, NULL, NULL, NULL, SYS_MSG, 0, 0, msg);
}
FriendsList api_get_friendslist(void)
{
return Friends;
}
char *api_get_nick(void)
{
size_t len = tox_self_get_name_size(user_tox);
uint8_t *name = malloc(len + 1);
if (name == NULL) {
return NULL;
}
tox_self_get_name(user_tox, name);
name[len] = '\0';
return (char *) name;
}
Tox_User_Status api_get_status(void)
{
return tox_self_get_status(user_tox);
}
char *api_get_status_message(void)
{
size_t len = tox_self_get_status_message_size(user_tox);
uint8_t *status = malloc(len + 1);
if (status == NULL) {
return NULL;
}
tox_self_get_status_message(user_tox, status);
status[len] = '\0';
return (char *) status;
}
void api_send(const char *msg)
{
if (msg == NULL || self_window->chatwin->cqueue == NULL) {
return;
}
char *name = api_get_nick();
if (name == NULL) {
return;
}
self_window = get_active_window();
strncpy((char *) self_window->chatwin->line, msg, sizeof(self_window->chatwin->line));
add_line_to_hist(self_window->chatwin);
int id = line_info_add(self_window, true, name, NULL, OUT_MSG, 0, 0, "%s", msg);
cqueue_add(self_window->chatwin->cqueue, msg, strlen(msg), OUT_MSG, id);
free(name);
}
void api_execute(const char *input, int mode)
{
self_window = get_active_window();
execute(cur_window, self_window, user_tox, input, mode);
}
int do_plugin_command(int num_args, char (*args)[MAX_STR_SIZE])
{
return do_python_command(num_args, args);
}
int num_registered_handlers(void)
{
return python_num_registered_handlers();
}
int help_max_width(void)
{
return python_help_max_width();
}
void draw_handler_help(WINDOW *win)
{
python_draw_handler_help(win);
}
void cmd_run(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
FILE *fp;
const char *error_str;
cur_window = window;
self_window = self;
if (argc != 1) {
if (argc < 1) {
error_str = "Path must be specified.";
} else {
error_str = "Only one argument allowed.";
}
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, error_str);
return;
}
fp = fopen(argv[1], "r");
if (fp == NULL) {
error_str = "Path does not exist.";
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, error_str);
return;
}
run_python(fp, argv[1]);
fclose(fp);
}
void invoke_autoruns(WINDOW *window, ToxWindow *self)
{
char abspath_buf[PATH_MAX + 256];
char err_buf[PATH_MAX + 128];
if (user_settings->autorun_path[0] == '\0') {
return;
}
DIR *d = opendir(user_settings->autorun_path);
if (d == NULL) {
snprintf(err_buf, sizeof(err_buf), "Autorun path does not exist: %s", user_settings->autorun_path);
api_display(err_buf);
return;
}
struct dirent *dir = NULL;
cur_window = window;
self_window = self;
while ((dir = readdir(d)) != NULL) {
size_t path_len = strlen(dir->d_name);
if (!strcmp(dir->d_name + path_len - 3, ".py")) {
snprintf(abspath_buf, sizeof(abspath_buf), "%s%s", user_settings->autorun_path, dir->d_name);
FILE *fp = fopen(abspath_buf, "r");
if (fp == NULL) {
snprintf(err_buf, sizeof(err_buf), "Invalid path: %s", abspath_buf);
api_display(err_buf);
continue;
}
run_python(fp, abspath_buf);
fclose(fp);
}
}
closedir(d);
}
#endif /* PYTHON */

43
src/api.h Normal file
View File

@ -0,0 +1,43 @@
/* api.h
*
*
* Copyright (C) 2017 Jakob Kreuze <jakob@memeware.net>
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef API_H
#define API_H
#include "friendlist.h"
#include "windows.h"
void api_display(const char *const msg);
FriendsList api_get_friendslist(void);
char *api_get_nick(void);
Tox_User_Status api_get_status(void);
char *api_get_status_message(void);
void api_send(const char *msg);
void api_execute(const char *input, int mode);
int do_plugin_command(int num_args, char (*args)[MAX_STR_SIZE]);
int num_registered_handlers(void);
int help_max_width(void);
void draw_handler_help(WINDOW *win);
void invoke_autoruns(WINDOW *w, ToxWindow *self);
void cmd_run(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
#endif /* API_H */

File diff suppressed because it is too large Load Diff

View File

@ -27,9 +27,7 @@
#include "audio_device.h"
#define MAX_CALLS 10
typedef enum _AudioError {
typedef enum AudioError {
ae_None = 0,
ae_StartingCaptureDevice = 1 << 0,
ae_StartingOutputDevice = 1 << 1,
@ -37,7 +35,7 @@ typedef enum _AudioError {
} AudioError;
#ifdef VIDEO
typedef enum _VideoError {
typedef enum VideoError {
ve_None = 0,
ve_StartingCaptureDevice = 1 << 0,
ve_StartingOutputDevice = 1 << 1,
@ -46,14 +44,27 @@ typedef enum _VideoError {
#endif /* VIDEO */
/* Status transitions:
* None -> Pending (call invitation made or received);
* Pending -> None (invitation rejected or failed);
* Pending -> Active (call starts);
* Active -> None (call ends).
*/
typedef enum CallStatus {
cs_None = 0,
cs_Pending,
cs_Active
} CallStatus;
typedef struct Call {
pthread_t ttid; /* Transmission thread id */
bool ttas, has_output; /* Transmission thread active status (0 - stopped, 1- running) */
uint32_t in_idx, out_idx; /* Audio Index */
#ifdef VIDEO
uint32_t vin_idx, vout_idx; /* Video Index */
#endif /* VIDEO */
pthread_mutex_t mutex;
CallStatus status;
uint32_t state; /* ToxAV call state, valid when `status == cs_Active` */
uint32_t in_idx, out_idx; /* Audio device index, or -1 if not open */
uint32_t audio_bit_rate; /* Bit rate for sending audio */
uint32_t vin_idx, vout_idx; /* Video device index, or -1 if not open */
uint32_t video_width, video_height;
uint32_t video_bit_rate; /* Bit rate for sending video; 0 for no video */
} Call;
struct CallControl {
@ -65,29 +76,34 @@ struct CallControl {
ToxAV *av;
ToxWindow *prompt;
Call calls[MAX_CALLS];
uint32_t call_state;
bool pending_call;
Call *calls;
uint32_t max_calls;
bool audio_enabled;
bool video_enabled;
uint32_t audio_bit_rate;
int32_t audio_frame_duration;
uint32_t audio_sample_rate;
uint8_t audio_channels;
uint32_t default_audio_bit_rate;
uint32_t video_bit_rate;
int32_t video_frame_duration;
uint32_t default_video_width, default_video_height;
uint32_t default_video_bit_rate;
};
} CallControl;
struct CallControl CallControl;
extern struct CallControl CallControl;
/* You will have to pass pointer to first member of 'windows' declared in windows.c */
ToxAV *init_audio(ToxWindow *self, Tox *tox);
void terminate_audio();
int start_transmission(ToxWindow *self, Call *call);
int stop_transmission(Call *call, uint32_t friend_number);
void terminate_audio(void);
bool init_call(Call *call);
void place_call(ToxWindow *self);
void stop_current_call(ToxWindow *self);
void init_friend_AV(uint32_t index);
void del_friend_AV(uint32_t index);
#endif /* AUDIO_CALL_H */

File diff suppressed because it is too large Load Diff

View File

@ -30,8 +30,9 @@
#define AUDIO_DEVICE_H
#define OPENAL_BUFS 5
#define MAX_OPENAL_DEVICES 32
#define MAX_DEVICES 32
#include <inttypes.h>
#include "windows.h"
typedef enum DeviceType {
@ -52,44 +53,45 @@ typedef enum DeviceError {
de_AlError = -9,
} DeviceError;
typedef void (*DataHandleCallback) (const int16_t *, uint32_t size, void *data);
typedef void (*DataHandleCallback)(const int16_t *, uint32_t size, void *data);
#ifdef AUDIO
DeviceError init_devices(ToxAV *av);
#else
DeviceError init_devices();
#endif /* AUDIO */
DeviceError init_devices(void);
DeviceError terminate_devices();
/* Callback handles ready data from INPUT device */
DeviceError register_device_callback(int32_t friend_number, uint32_t device_idx, DataHandleCallback callback,
void *data, bool enable_VAD);
void *get_device_callback_data(uint32_t device_idx);
void get_al_device_names(void);
DeviceError terminate_devices(void);
/* toggle device mute */
DeviceError device_mute(DeviceType type, uint32_t device_idx);
#ifdef AUDIO
DeviceError device_set_VAD_treshold(uint32_t device_idx, float value);
#endif
bool device_is_muted(DeviceType type, uint32_t device_idx);
DeviceError device_set_VAD_threshold(uint32_t device_idx, float value);
float device_get_VAD_threshold(uint32_t device_idx);
DeviceError set_source_position(uint32_t device_idx, float x, float y, float z);
DeviceError set_al_device(DeviceType type, int32_t selection);
DeviceError set_primary_device(DeviceType type, int32_t selection);
DeviceError open_primary_device(DeviceType type, uint32_t *device_idx, uint32_t sample_rate, uint32_t frame_duration,
uint8_t channels);
/* Start device */
DeviceError open_device(DeviceType type, int32_t selection, uint32_t *device_idx, uint32_t sample_rate,
uint32_t frame_duration, uint8_t channels);
DeviceError open_input_device(uint32_t *device_idx,
DataHandleCallback cb, void *cb_data, bool enable_VAD,
uint32_t sample_rate, uint32_t frame_duration, uint8_t channels);
DeviceError open_output_device(uint32_t *device_idx,
uint32_t sample_rate, uint32_t frame_duration, uint8_t channels);
/* Stop device */
DeviceError close_device(DeviceType type, uint32_t device_idx);
/* Write data to device */
/* Write data to output device */
DeviceError write_out(uint32_t device_idx, const int16_t *data, uint32_t length, uint8_t channels,
uint32_t sample_rate);
void print_devices(ToxWindow *self, DeviceType type);
void get_primary_device_name(DeviceType type, char *buf, int size);
/* return current input volume as float in range 0.0-100.0 */
float get_input_volume(void);
void print_al_devices(ToxWindow *self, DeviceType type);
DeviceError selection_valid(DeviceType type, int32_t selection);
#endif /* AUDIO_DEVICE_H */

View File

@ -20,36 +20,35 @@
*
*/
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#ifdef __APPLE__
#include <sys/types.h>
#include <sys/dir.h>
#else
#include <dirent.h>
#endif /* ifdef __APPLE__ */
#endif /* __APPLE__ */
#include "windows.h"
#include "toxic.h"
#include "misc_tools.h"
#include "line_info.h"
#include "execute.h"
#include "configdir.h"
#include "execute.h"
#include "line_info.h"
#include "misc_tools.h"
#include "toxic.h"
#include "windows.h"
static void print_matches(ToxWindow *self, Tox *m, const void *list, int n_items, int size)
static void print_ac_matches(ToxWindow *self, Tox *m, char **list, size_t n_matches)
{
if (m)
if (m) {
execute(self->chatwin->history, self, m, "/clear", GLOBAL_COMMAND_MODE);
}
const char *L = (char *) list;
int i;
for (size_t i = 0; i < n_matches; ++i) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", list[i]);
}
for (i = 0; i < n_items; ++i)
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", &L[i * size]);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, ""); /* formatting */
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "");
}
/* puts match in match buffer. if more than one match, add first n chars that are identical.
@ -57,19 +56,19 @@ static void print_matches(ToxWindow *self, Tox *m, const void *list, int n_items
*
* Returns the length of the match.
*/
static size_t get_str_match(ToxWindow *self, char *match, size_t match_sz, char (*matches)[MAX_STR_SIZE], int n)
static size_t get_str_match(ToxWindow *self, char *match, size_t match_sz, const char **matches, size_t n_items,
size_t max_size)
{
if (n == 1) {
UNUSED_VAR(self);
if (n_items == 1) {
return snprintf(match, match_sz, "%s", matches[0]);
}
int i;
for (i = 0; i < MAX_STR_SIZE; ++i) {
for (size_t i = 0; i < max_size; ++i) {
char ch1 = matches[0][i];
int j;
for (j = 0; j < n; ++j) {
for (size_t j = 0; j < n_items; ++j) {
char ch2 = matches[j][i];
if (ch1 != ch2 || !ch1) {
@ -83,49 +82,58 @@ static size_t get_str_match(ToxWindow *self, char *match, size_t match_sz, char
return snprintf(match, match_sz, "%s", matches[0]);
}
/* looks for all instances in list that begin with the last entered word in line according to pos,
then fills line with the complete word. e.g. "Hello jo" would complete the line
with "Hello john". If multiple matches, prints out all the matches and semi-completes line.
list is a pointer to the list of strings being compared, n_items is the number of items
in the list, and size is the size of each item in the list.
Returns the difference between the old len and new len of line on success, -1 if error */
int complete_line(ToxWindow *self, const void *list, int n_items, int size)
/*
* Looks for all instances in list that begin with the last entered word in line according to pos,
* then fills line with the complete word. e.g. "Hello jo" would complete the line
* with "Hello john". If multiple matches, prints out all the matches and semi-completes line.
*
* `list` is a pointer to `n_items` strings. Each string in the list must be <= MAX_STR_SIZE.
*
* dir_search should be true if the line being completed is a file path.
*
* Returns the difference between the old len and new len of line on success.
* Returns -1 on error.
*
* Note: This function should not be called directly. Use complete_line() and complete_path() instead.
*/
static int complete_line_helper(ToxWindow *self, const char **list, const size_t n_items, bool dir_search)
{
ChatContext *ctx = self->chatwin;
if (ctx->pos <= 0 || ctx->len <= 0 || ctx->pos > ctx->len || ctx->len >= MAX_STR_SIZE || size > MAX_STR_SIZE)
if (ctx->pos <= 0 || ctx->len <= 0 || ctx->pos > ctx->len) {
return -1;
}
if (ctx->len >= MAX_STR_SIZE) {
return -1;
}
const char *L = (char *) list;
const char *endchrs = " ";
char ubuf[MAX_STR_SIZE];
/* work with multibyte string copy of buf for simplicity */
if (wcs_to_mbs_buf(ubuf, ctx->line, sizeof(ubuf)) == -1)
if (wcs_to_mbs_buf(ubuf, ctx->line, sizeof(ubuf)) == -1) {
return -1;
/* TODO: generalize this */
bool dir_search = !strncmp(ubuf, "/sendfile", strlen("/sendfile"))
|| !strncmp(ubuf, "/avatar", strlen("/avatar"));
}
/* isolate substring from space behind pos to pos */
char tmp[MAX_STR_SIZE];
snprintf(tmp, sizeof(tmp), "%s", ubuf);
tmp[ctx->pos] = '\0';
memcpy(tmp, ubuf, ctx->pos);
tmp[ctx->pos] = 0;
const char *s = dir_search ? strchr(tmp, '\"') : strrchr(tmp, ' ');
const char *s = dir_search ? strchr(tmp, ' ') : strrchr(tmp, ' ');
char *sub = calloc(1, strlen(ubuf) + 1);
if (sub == NULL)
exit_toxic_err("failed in complete_line", FATALERR_MEMORY);
if (sub == NULL) {
exit_toxic_err("failed in complete_line_helper", FATALERR_MEMORY);
}
if (!s && !dir_search) {
strcpy(sub, tmp);
if (sub[0] != '/')
if (sub[0] != '/') {
endchrs = ": ";
}
} else if (s) {
strcpy(sub, &s[1]);
@ -133,50 +141,60 @@ int complete_line(ToxWindow *self, const void *list, int n_items, int size)
int sub_len = strlen(sub);
int si = char_rfind(sub, '/', sub_len);
if (si || *sub == '/')
if (si || *sub == '/') {
memmove(sub, &sub[si + 1], sub_len - si);
}
}
}
if (!sub[0]) {
if (!sub[0] && !(dir_search && n_items == 1)) {
free(sub);
return 0;
}
int s_len = strlen(sub);
size_t n_matches = 0;
char **matches = (char **) malloc_ptr_array(n_items, MAX_STR_SIZE);
if (matches == NULL) {
free(sub);
return -1;
}
int s_len = strlen(sub);
int n_matches = 0;
char matches[n_items][MAX_STR_SIZE];
int i = 0;
/* put all list matches in matches array */
for (i = 0; i < n_items; ++i) {
char str[MAX_CMDNAME_SIZE + 1];
snprintf(str, sizeof(str), "%s", &L[i * size]);
if (strncasecmp(str, sub, s_len) == 0)
strcpy(matches[n_matches++], str);
for (size_t i = 0; i < n_items; ++i) {
if (strncasecmp(list[i], sub, s_len) == 0) {
snprintf(matches[n_matches++], MAX_STR_SIZE, "%s", list[i]);
}
}
free(sub);
if (!n_matches)
if (!n_matches) {
free_ptr_array((void **) matches);
return -1;
}
if (!dir_search && n_matches > 1)
print_matches(self, NULL, matches, n_matches, MAX_STR_SIZE);
if (!dir_search && n_matches > 1) {
print_ac_matches(self, NULL, matches, n_matches);
}
char match[MAX_STR_SIZE];
size_t match_len = get_str_match(self, match, sizeof(match), matches, n_matches);
size_t match_len = get_str_match(self, match, sizeof(match), (const char **) matches, n_matches, MAX_STR_SIZE);
free_ptr_array((void **) matches);
if (match_len == 0) {
return 0;
}
if (dir_search) {
if (n_matches == 1)
endchrs = char_rfind(match, '.', match_len) ? "\"" : "/";
else
if (n_matches == 1) {
endchrs = char_rfind(match, '.', match_len) ? "" : "/";
} else {
endchrs = "";
}
} else if (n_matches > 1) {
endchrs = "";
}
@ -186,8 +204,9 @@ int complete_line(ToxWindow *self, const void *list, int n_items, int size)
int strt = ctx->pos - s_len;
int diff = match_len - s_len + n_endchrs;
if (ctx->len + diff >= MAX_STR_SIZE)
if (ctx->len + diff >= MAX_STR_SIZE) {
return -1;
}
char tmpend[MAX_STR_SIZE];
snprintf(tmpend, sizeof(tmpend), "%s", &ubuf[ctx->pos]);
@ -197,6 +216,31 @@ int complete_line(ToxWindow *self, const void *list, int n_items, int size)
}
strcpy(&ubuf[strt], match);
/* If path points to a file with no extension don't append a forward slash */
if (dir_search && *endchrs == '/') {
const char *path_start = strchr(ubuf + 1, '/');
if (!path_start) {
path_start = strchr(ubuf + 1, ' ');
if (!path_start) {
return -1;
}
}
if (strlen(path_start) < 2) {
return -1;
}
++path_start;
if (file_type(path_start) == FILE_TYPE_REGULAR) {
endchrs = "";
diff -= n_endchrs;
}
}
strcpy(&ubuf[strt + match_len], endchrs);
strcpy(&ubuf[strt + match_len + n_endchrs], tmpend);
@ -215,54 +259,82 @@ int complete_line(ToxWindow *self, const void *list, int n_items, int size)
return diff;
}
/* transforms a tab complete starting with the shorthand "~" into the full home directory.*/
static void complt_home_dir(ToxWindow *self, char *path, int pathsize, const char *cmd, int cmdlen)
int complete_line(ToxWindow *self, const char **list, size_t n_items)
{
return complete_line_helper(self, list, n_items, false);
}
static int complete_path(ToxWindow *self, const char **list, const size_t n_items)
{
return complete_line_helper(self, list, n_items, true);
}
/* Transforms a tab complete starting with the shorthand "~" into the full home directory. */
static void complete_home_dir(ToxWindow *self, char *path, int pathsize, const char *cmd, int cmdlen)
{
ChatContext *ctx = self->chatwin;
char homedir[MAX_STR_SIZE] = {0};
get_home_dir(homedir, sizeof(homedir));
char newline[MAX_STR_SIZE];
snprintf(newline, sizeof(newline), "%s \"%s%s", cmd, homedir, path + 1);
snprintf(path, pathsize, "%s", &newline[cmdlen]);
char newline[MAX_STR_SIZE + 1];
snprintf(newline, sizeof(newline), "%s %s%s", cmd, homedir, path + 1);
snprintf(path, pathsize, "%s", &newline[cmdlen - 1]);
wchar_t wline[MAX_STR_SIZE];
if (mbs_to_wcs_buf(wline, newline, sizeof(wline) / sizeof(wchar_t)) == -1)
if (mbs_to_wcs_buf(wline, newline, sizeof(wline) / sizeof(wchar_t)) == -1) {
return;
}
int newlen = wcslen(wline);
if (ctx->len + newlen >= MAX_STR_SIZE)
if (ctx->len + newlen >= MAX_STR_SIZE) {
return;
}
wmemcpy(ctx->line, wline, newlen + 1);
ctx->pos = newlen;
ctx->len = ctx->pos;
}
/* attempts to match /command "<incomplete-dir>" line to matching directories.
/*
* Return true if the first `p_len` chars in `s` are equal to `p` and `s` is a valid directory name.
*/
static bool is_partial_match(const char *s, const char *p, size_t p_len)
{
if (s == NULL || p == NULL) {
return false;
}
if only one match, auto-complete line.
return diff between old len and new len of ctx->line, -1 if no matches or > 1 match */
#define MAX_DIRS 512
return strncmp(s, p, p_len) == 0 && strcmp(".", s) != 0 && strcmp("..", s) != 0;
}
/* Attempts to match /command "<incomplete-dir>" line to matching directories.
* If there is only one match the line is auto-completed.
*
* Returns the diff between old len and new len of ctx->line on success.
* Returns -1 if no matches or more than one match.
*/
#define MAX_DIRS 75
int dir_match(ToxWindow *self, Tox *m, const wchar_t *line, const wchar_t *cmd)
{
char b_path[MAX_STR_SIZE];
char b_name[MAX_STR_SIZE];
char b_path[MAX_STR_SIZE + 1];
char b_name[MAX_STR_SIZE + 1];
char b_cmd[MAX_STR_SIZE];
const wchar_t *tmpline = &line[wcslen(cmd) + 2]; /* start after "/command \"" */
const wchar_t *tmpline = &line[wcslen(cmd) + 1]; /* start after "/command " */
if (wcs_to_mbs_buf(b_path, tmpline, sizeof(b_path)) == -1)
if (wcs_to_mbs_buf(b_path, tmpline, sizeof(b_path) - 1) == -1) {
return -1;
}
if (wcs_to_mbs_buf(b_cmd, cmd, sizeof(b_cmd)) == -1)
if (wcs_to_mbs_buf(b_cmd, cmd, sizeof(b_cmd)) == -1) {
return -1;
}
if (b_path[0] == '~')
complt_home_dir(self, b_path, sizeof(b_path), b_cmd, strlen(b_cmd) + 2);
if (b_path[0] == '~') {
complete_home_dir(self, b_path, sizeof(b_path) - 1, b_cmd, strlen(b_cmd) + 2);
}
int si = char_rfind(b_path, '/', strlen(b_path));
@ -270,40 +342,52 @@ int dir_match(ToxWindow *self, Tox *m, const wchar_t *line, const wchar_t *cmd)
b_path[0] = '.';
b_path[1] = '\0';
} else if (!si && b_path[0] != '/') { /* look for matches in pwd */
char tmp[MAX_STR_SIZE];
snprintf(tmp, sizeof(tmp), ".%s", b_path);
snprintf(b_path, sizeof(b_path), "%s", tmp);
memmove(b_path + 1, b_path, sizeof(b_path) - 1);
b_path[0] = '.';
}
snprintf(b_name, sizeof(b_name), "%s", &b_path[si + 1]);
b_path[si + 1] = '\0';
int b_name_len = strlen(b_name);
size_t b_name_len = strlen(b_name);
DIR *dp = opendir(b_path);
if (dp == NULL)
if (dp == NULL) {
return -1;
}
char **dirnames = (char **) malloc_ptr_array(MAX_DIRS, NAME_MAX + 1);
if (dirnames == NULL) {
closedir(dp);
return -1;
}
char dirnames[MAX_DIRS][NAME_MAX];
struct dirent *entry;
int dircount = 0;
while ((entry = readdir(dp)) && dircount < MAX_DIRS) {
if (strncmp(entry->d_name, b_name, b_name_len) == 0
&& strcmp(".", entry->d_name) && strcmp("..", entry->d_name)) {
snprintf(dirnames[dircount], sizeof(dirnames[dircount]), "%s", entry->d_name);
if (is_partial_match(entry->d_name, b_name, b_name_len)) {
snprintf(dirnames[dircount], NAME_MAX + 1, "%s", entry->d_name);
++dircount;
}
}
closedir(dp);
if (dircount == 0)
if (dircount == 0) {
free_ptr_array((void **) dirnames);
return -1;
if (dircount > 1) {
qsort(dirnames, dircount, NAME_MAX, qsort_strcasecmp_hlpr);
print_matches(self, m, dirnames, dircount, NAME_MAX);
}
return complete_line(self, dirnames, dircount, NAME_MAX);
if (dircount > 1) {
qsort(dirnames, dircount, sizeof(char *), qsort_ptr_char_array_helper);
print_ac_matches(self, m, dirnames, dircount);
}
int ret = complete_path(self, (const char **) dirnames, dircount);
free_ptr_array((void **) dirnames);
return ret;
}

View File

@ -23,20 +23,30 @@
#ifndef AUTOCOMPLETE_H
#define AUTOCOMPLETE_H
/* looks for all instances in list that begin with the last entered word in line according to pos,
then fills line with the complete word. e.g. "Hello jo" would complete the line
with "Hello john". If multiple matches, prints out all the matches and semi-completes line.
#include "windows.h"
list is a pointer to the list of strings being compared, n_items is the number of items
in the list, and size is the size of each item in the list.
/*
* Looks for all instances in list that begin with the last entered word in line according to pos,
* then fills line with the complete word. e.g. "Hello jo" would complete the line
* with "Hello john". If multiple matches, prints out all the matches and semi-completes line.
*
* `list` is a pointer to `n_items` strings.
*
* dir_search should be true if the line being completed is a file path.
*
* Returns the difference between the old len and new len of line on success.
* Returns -1 on error.
*
* Note: This function should not be called directly. Use complete_line() and complete_path() instead.
*/
int complete_line(ToxWindow *self, const char **list, size_t n_items);
Returns the difference between the old len and new len of line on success, -1 if error */
int complete_line(ToxWindow *self, const void *list, int n_items, int size);
/* attempts to match /command "<incomplete-dir>" line to matching directories.
if only one match, auto-complete line.
return diff between old len and new len of ctx->line, -1 if no matches or > 1 match */
/* Attempts to match /command "<incomplete-dir>" line to matching directories.
* If there is only one match the line is auto-completed.
*
* Returns the diff between old len and new len of ctx->line on success.
* Returns -1 if no matches or more than one match.
*/
int dir_match(ToxWindow *self, Tox *m, const wchar_t *line, const wchar_t *cmd);
#endif /* #define AUTOCOMPLETE_H */
#endif /* AUTOCOMPLETE_H */

View File

@ -20,14 +20,14 @@
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "misc_tools.h"
#include "avatars.h"
#include "file_transfers.h"
#include "friendlist.h"
#include "avatars.h"
#include "misc_tools.h"
extern FriendsList Friends;
@ -39,40 +39,76 @@ static struct Avatar {
off_t size;
} Avatar;
/* Compares the first size bytes of fp to signature.
*
* Returns 0 if they are the same
* Returns 1 if they differ
* Returns -1 on error.
*
* On success this function will seek back to the beginning of fp.
*/
static int check_file_signature(const unsigned char *signature, size_t size, FILE *fp)
{
char *buf = malloc(size);
if (buf == NULL) {
return -1;
}
if (fread(buf, size, 1, fp) != 1) {
free(buf);
return -1;
}
int ret = memcmp(signature, buf, size);
free(buf);
if (fseek(fp, 0L, SEEK_SET) == -1) {
return -1;
}
return ret == 0 ? 0 : 1;
}
static void avatar_clear(void)
{
memset(&Avatar, 0, sizeof(struct Avatar));
Avatar = (struct Avatar) {
0
};
}
/* Sends avatar to friendnum.
/* Sends avatar to friendnumber.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int avatar_send(Tox *m, uint32_t friendnum)
int avatar_send(Tox *m, uint32_t friendnumber)
{
TOX_ERR_FILE_SEND err;
uint32_t filenum = tox_file_send(m, friendnum, TOX_FILE_KIND_AVATAR, (size_t) Avatar.size,
NULL, (uint8_t *) Avatar.name, Avatar.name_len, &err);
Tox_Err_File_Send err;
uint32_t filenumber = tox_file_send(m, friendnumber, TOX_FILE_KIND_AVATAR, (size_t) Avatar.size,
NULL, (uint8_t *) Avatar.name, Avatar.name_len, &err);
if (Avatar.size == 0)
if (Avatar.size == 0) {
return 0;
}
if (err != TOX_ERR_FILE_SEND_OK) {
fprintf(stderr, "tox_file_send failed for friendnumber %d (error %d)\n", friendnum, err);
fprintf(stderr, "tox_file_send failed for friendnumber %u (error %d)\n", friendnumber, err);
return -1;
}
struct FileTransfer *ft = new_file_transfer(NULL, friendnum, filenum, FILE_TRANSFER_SEND, TOX_FILE_KIND_AVATAR);
struct FileTransfer *ft = new_file_transfer(NULL, friendnumber, filenumber, FILE_TRANSFER_SEND, TOX_FILE_KIND_AVATAR);
if (!ft)
if (!ft) {
return -1;
}
ft->file = fopen(Avatar.path, "r");
if (ft->file == NULL)
if (ft->file == NULL) {
return -1;
}
snprintf(ft->file_name, sizeof(ft->file_name), "%s", Avatar.name);
ft->file_size = Avatar.size;
@ -83,11 +119,10 @@ int avatar_send(Tox *m, uint32_t friendnum)
/* Sends avatar to all friends */
static void avatar_send_all(Tox *m)
{
size_t i;
for (i = 0; i < Friends.max_idx; ++i) {
if (Friends.list[i].connection_status != TOX_CONNECTION_NONE)
for (size_t i = 0; i < Friends.max_idx; ++i) {
if (Friends.list[i].connection_status != TOX_CONNECTION_NONE) {
avatar_send(m, Friends.list[i].num);
}
}
}
@ -98,15 +133,17 @@ static void avatar_send_all(Tox *m)
*/
int avatar_set(Tox *m, const char *path, size_t path_len)
{
if (path_len == 0 || path_len >= sizeof(Avatar.path))
if (path_len == 0 || path_len >= sizeof(Avatar.path)) {
return -1;
}
FILE *fp = fopen(path, "rb");
if (fp == NULL)
if (fp == NULL) {
return -1;
}
char PNG_signature[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
unsigned char PNG_signature[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
if (check_file_signature(PNG_signature, sizeof(PNG_signature), fp) != 0) {
fclose(fp);
@ -117,8 +154,9 @@ int avatar_set(Tox *m, const char *path, size_t path_len)
off_t size = file_size(path);
if (size == 0 || size > MAX_AVATAR_FILE_SIZE)
if (size == 0 || size > MAX_AVATAR_FILE_SIZE) {
return -1;
}
get_file_name(Avatar.name, sizeof(Avatar.name), path);
Avatar.name_len = strlen(Avatar.name);
@ -142,7 +180,14 @@ void avatar_unset(Tox *m)
avatar_send_all(m);
}
void on_avatar_file_control(Tox *m, struct FileTransfer *ft, TOX_FILE_CONTROL control)
void on_avatar_friend_connection_status(Tox *m, uint32_t friendnumber, Tox_Connection connection_status)
{
if (connection_status == TOX_CONNECTION_NONE) {
kill_avatar_file_transfers_friend(m, friendnumber);
}
}
void on_avatar_file_control(Tox *m, struct FileTransfer *ft, Tox_File_Control control)
{
switch (control) {
case TOX_FILE_CONTROL_RESUME:
@ -166,8 +211,9 @@ void on_avatar_file_control(Tox *m, struct FileTransfer *ft, TOX_FILE_CONTROL co
void on_avatar_chunk_request(Tox *m, struct FileTransfer *ft, uint64_t position, size_t length)
{
if (ft->state != FILE_TRANSFER_STARTED)
if (ft->state != FILE_TRANSFER_STARTED) {
return;
}
if (length == 0) {
close_file_transfer(NULL, m, ft, -1, NULL, silent);
@ -188,20 +234,29 @@ void on_avatar_chunk_request(Tox *m, struct FileTransfer *ft, uint64_t position,
ft->position = position;
}
uint8_t send_data[length];
size_t send_length = fread(send_data, 1, sizeof(send_data), ft->file);
uint8_t *send_data = malloc(length);
if (send_length != length) {
if (send_data == NULL) {
close_file_transfer(NULL, m, ft, TOX_FILE_CONTROL_CANCEL, NULL, silent);
return;
}
TOX_ERR_FILE_SEND_CHUNK err;
tox_file_send_chunk(m, ft->friendnum, ft->filenum, position, send_data, send_length, &err);
size_t send_length = fread(send_data, 1, length, ft->file);
if (err != TOX_ERR_FILE_SEND_CHUNK_OK)
if (send_length != length) {
close_file_transfer(NULL, m, ft, TOX_FILE_CONTROL_CANCEL, NULL, silent);
free(send_data);
return;
}
Tox_Err_File_Send_Chunk err;
tox_file_send_chunk(m, ft->friendnumber, ft->filenumber, position, send_data, send_length, &err);
free(send_data);
if (err != TOX_ERR_FILE_SEND_CHUNK_OK) {
fprintf(stderr, "tox_file_send_chunk failed in avatar callback (error %d)\n", err);
}
ft->position += send_length;
ft->last_keep_alive = get_unix_time();
}

View File

@ -23,6 +23,8 @@
#ifndef AVATARS_H
#define AVATARS_H
#include "file_transfers.h"
#define MAX_AVATAR_FILE_SIZE 65536
/* Sends avatar to friendnum.
@ -47,6 +49,7 @@ int avatar_set(Tox *m, const char *path, size_t length);
void avatar_unset(Tox *m);
void on_avatar_chunk_request(Tox *m, struct FileTransfer *ft, uint64_t position, size_t length);
void on_avatar_file_control(Tox *m, struct FileTransfer *ft, TOX_FILE_CONTROL control);
void on_avatar_file_control(Tox *m, struct FileTransfer *ft, Tox_File_Control control);
void on_avatar_friend_connection_status(Tox *m, uint32_t friendnumber, Tox_Connection connection_status);
#endif /* AVATARS_H */

View File

@ -20,20 +20,24 @@
*
*/
#include <arpa/inet.h>
#include <limits.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <limits.h>
#include <sys/socket.h>
#include <curl/curl.h>
#include <tox/tox.h>
#include "line_info.h"
#include "windows.h"
#include "misc_tools.h"
#include "configdir.h"
#include "curl_util.h"
#include "line_info.h"
#include "misc_tools.h"
#include "prompt.h"
#include "settings.h"
#include "windows.h"
extern struct arg_opts arg_opts;
extern struct user_settings *user_settings;
@ -104,11 +108,45 @@ static struct DHT_Nodes {
time_t last_updated;
} Nodes;
/* Return true if address appears to be a valid ipv4 address. */
static bool is_ip4_address(const char *address)
{
struct sockaddr_in s_addr;
return inet_pton(AF_INET, address, &(s_addr.sin_addr)) != 0;
}
/* Return true if address roughly appears to be a valid ipv6 address.
*
* TODO: Improve this function (inet_pton behaves strangely with ipv6).
* for now the only guarantee is that it won't return true if the
* address is a domain or ipv4 address, and should only be used if you're
* reasonably sure that the address is one of the three (ipv4, ipv6 or a domain).
*/
static bool is_ip6_address(const char *address)
{
size_t num_colons = 0;
char ch = 0;
for (size_t i = 0; (ch = address[i]); ++i) {
if (ch == '.') {
return false;
}
if (ch == ':') {
++num_colons;
}
}
return num_colons > 1 && num_colons < 8;
}
/* Determine if a node is offline by comparing the age of the nodeslist
* to the last time the node was successfully pinged.
*/
#define NODE_IS_OFFLINE(last_scan, last_ping) ((last_ping + NODE_OFFLINE_TIMOUT) <= (last_ping))
static bool node_is_offline(unsigned long long int last_ping)
{
return last_ping + NODE_OFFLINE_TIMOUT <= last_ping;
}
/* Return true if nodeslist pointed to by fp needs to be updated.
* This will be the case if the file is empty, has an invalid format,
@ -237,6 +275,7 @@ on_exit:
* Return -2 if http lookup failed.
* Return -3 if http reponse was empty.
* Return -4 if data could not be written to disk.
* Return -5 if memory allocation fails.
*/
static int update_DHT_nodeslist(const char *nodes_path)
{
@ -250,26 +289,34 @@ static int update_DHT_nodeslist(const char *nodes_path)
return -1;
}
struct Recv_Curl_Data recv_data;
struct Recv_Curl_Data *recv_data = calloc(1, sizeof(struct Recv_Curl_Data));
memset(&recv_data, 0, sizeof(struct Recv_Curl_Data));
if (recv_data == NULL) {
fclose(fp);
return -5;
}
if (curl_fetch_nodes_JSON(&recv_data) == -1) {
if (curl_fetch_nodes_JSON(recv_data) == -1) {
free(recv_data);
fclose(fp);
return -2;
}
if (recv_data.length == 0) {
if (recv_data->length == 0) {
free(recv_data);
fclose(fp);
return -3;
}
if (fwrite(recv_data.data, recv_data.length, 1, fp) != 1) {
if (fwrite(recv_data->data, recv_data->length, 1, fp) != 1) {
free(recv_data);
fclose(fp);
return -4;
}
free(recv_data);
fclose(fp);
return 1;
}
@ -377,7 +424,7 @@ static int extract_node(const char *line, struct Node *node)
long long int last_pinged = extract_val_last_pinged(last_pinged_str + LAST_PING_JSON_KEY_LEN);
if (last_pinged <= 0 || NODE_IS_OFFLINE(Nodes.last_scan, last_pinged)) {
if (last_pinged <= 0 || node_is_offline(last_pinged)) {
return -3;
}
@ -426,6 +473,8 @@ static int extract_node(const char *line, struct Node *node)
/* Loads the DHT nodeslist to memory from json encoded nodes file. */
void *load_nodeslist_thread(void *data)
{
UNUSED_VAR(data);
char nodes_path[PATH_MAX];
get_nodeslist_path(nodes_path, sizeof(nodes_path));
@ -477,7 +526,7 @@ void *load_nodeslist_thread(void *data)
/* If nodeslist does not contain any valid entries we set the last_scan value
* to 0 so that it will fetch a new list the next time this function is called.
*/
if (idx == 0) {
if (Nodes.count == 0) {
const char *s = "{\"last_scan\":0}";
rewind(fp);
fwrite(s, strlen(s), 1, fp); // Not much we can do if it fails
@ -556,7 +605,7 @@ static void DHT_bootstrap(Tox *m)
continue;
}
TOX_ERR_BOOTSTRAP err;
Tox_Err_Bootstrap err;
tox_bootstrap(m, addr, node->port, (uint8_t *) node->key, &err);
if (err != TOX_ERR_BOOTSTRAP_OK) {
@ -577,7 +626,7 @@ static void DHT_bootstrap(Tox *m)
void do_tox_connection(Tox *m)
{
static time_t last_bootstrap_time = 0;
bool connected = tox_self_get_connection_status(m) != TOX_CONNECTION_NONE;
bool connected = prompt_selfConnectionStatus() != TOX_CONNECTION_NONE;
if (!connected && timed_out(last_bootstrap_time, TRY_BOOTSTRAP_INTERVAL)) {
DHT_bootstrap(m);

View File

@ -39,4 +39,4 @@ void do_tox_connection(Tox *m);
*/
int load_DHT_nodeslist(void);
#endif /* BOOTSTRAP_H */
#endif /* BOOTSTRAP_H */

1110
src/chat.c

File diff suppressed because it is too large Load Diff

View File

@ -23,13 +23,13 @@
#ifndef CHAT_H
#define CHAT_H
#include "windows.h"
#include "toxic.h"
#include "windows.h"
/* set CTRL to -1 if we don't want to send a control signal.
set msg to NULL if we don't want to display a message */
void chat_close_file_receiver(Tox *m, int filenum, int friendnum, int CTRL);
void kill_chat_window(ToxWindow *self, Tox *m);
ToxWindow new_chat(Tox *m, int32_t friendnum);
ToxWindow *new_chat(Tox *m, int32_t friendnum);
#endif /* end of include guard: CHAT_H */

View File

@ -23,23 +23,25 @@
#include <stdlib.h>
#include <string.h>
#include "chat.h"
#include "conference.h"
#include "execute.h"
#include "file_transfers.h"
#include "friendlist.h"
#include "line_info.h"
#include "misc_tools.h"
#include "toxic.h"
#include "windows.h"
#include "misc_tools.h"
#include "friendlist.h"
#include "execute.h"
#include "line_info.h"
#include "groupchat.h"
#include "chat.h"
#include "file_transfers.h"
extern ToxWindow *prompt;
extern FriendsList Friends;
void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc < 2) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Requires type in|out and the file ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Requires type in|out and the file ID.");
return;
}
@ -48,7 +50,7 @@ void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar
long int idx = strtol(argv[2], NULL, 10);
if ((idx == 0 && strcmp(argv[2], "0")) || idx >= MAX_FILES || idx < 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid file ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid file ID.");
return;
}
@ -60,17 +62,17 @@ void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar
} else if (strcasecmp(inoutstr, "out") == 0) {
ft = get_file_transfer_struct_index(self->num, idx, FILE_TRANSFER_SEND);
} else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Type must be 'in' or 'out'.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Type must be 'in' or 'out'.");
return;
}
if (!ft) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid file ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid file ID.");
return;
}
if (ft->state == FILE_TRANSFER_INACTIVE) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid file ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid file ID.");
return;
}
@ -78,112 +80,196 @@ void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar
close_file_transfer(self, m, ft, TOX_FILE_CONTROL_CANCEL, msg, silent);
}
void cmd_groupinvite(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
void cmd_conference_invite(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Group number required.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Conference number required.");
return;
}
long int groupnum = strtol(argv[1], NULL, 10);
long int conferencenum = strtol(argv[1], NULL, 10);
if ((groupnum == 0 && strcmp(argv[1], "0")) || groupnum < 0 || groupnum == LONG_MAX) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid group number.");
if ((conferencenum == 0 && strcmp(argv[1], "0")) || conferencenum < 0 || conferencenum == LONG_MAX) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid conference number.");
return;
}
if (tox_invite_friend(m, self->num, groupnum) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to invite contact to group.");
Tox_Err_Conference_Invite err;
if (!tox_conference_invite(m, self->num, conferencenum, &err)) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to invite contact to conference (error %d)", err);
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invited contact to Group %d.", groupnum);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invited contact to Conference %ld.", conferencenum);
}
void cmd_join_group(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(argc);
UNUSED_VAR(argv);
if (get_num_active_windows() >= MAX_WINDOWS_NUM) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, " * Warning: Too many windows are open.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Warning: Too many windows are open.");
return;
}
const char *groupkey = Friends.list[self->num].group_invite.key;
uint16_t length = Friends.list[self->num].group_invite.length;
uint8_t type = Friends.list[self->num].group_invite.type;
const char *conferencekey = Friends.list[self->num].conference_invite.key;
uint16_t length = Friends.list[self->num].conference_invite.length;
uint8_t type = Friends.list[self->num].conference_invite.type;
if (!Friends.list[self->num].group_invite.pending) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending group chat invite.");
if (!Friends.list[self->num].conference_invite.pending) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending conference invite.");
return;
}
int groupnum = -1;
uint32_t conferencenum;
if (type == TOX_GROUPCHAT_TYPE_TEXT)
groupnum = tox_join_groupchat(m, self->num, (uint8_t *) groupkey, length);
if (type == TOX_CONFERENCE_TYPE_TEXT) {
Tox_Err_Conference_Join err;
conferencenum = tox_conference_join(m, self->num, (const uint8_t *) conferencekey, length, &err);
/*#ifdef AUDIO
else
groupnum = toxav_join_av_groupchat(m, self->num, (uint8_t *) groupkey, length,
NULL, NULL);
#endif*/
if (err != TOX_ERR_CONFERENCE_JOIN_OK) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Conference instance failed to initialize (error %d)", err);
return;
}
} else if (type == TOX_CONFERENCE_TYPE_AV) {
#ifdef AUDIO
conferencenum = toxav_join_av_groupchat(m, self->num, (const uint8_t *) conferencekey, length,
audio_conference_callback, NULL);
if (groupnum == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Group chat instance failed to initialize.");
if (conferencenum == (uint32_t) -1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Audio conference instance failed to initialize");
return;
}
#else
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Audio support disabled by compile-time option.");
return;
#endif
} else {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Unknown conference type %d", type);
return;
}
if (init_groupchat_win(prompt, m, groupnum, type) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Group chat window failed to initialize.");
tox_del_groupchat(m, groupnum);
if (init_conference_win(m, conferencenum, type, NULL, 0) == -1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Conference window failed to initialize.");
tox_conference_delete(m, conferencenum, NULL);
return;
}
#ifdef AUDIO
if (type == TOX_CONFERENCE_TYPE_AV) {
if (!init_conference_audio_input(m, conferencenum)) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Audio capture failed; use \"/audio on\" to try again.");
}
}
#endif
}
#ifdef GAMES
void cmd_game_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(m);
if (!Friends.list[self->num].game_invite.pending) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending game invite.");
return;
}
if (get_num_active_windows() >= MAX_WINDOWS_NUM) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Warning: Too many windows are open.");
return;
}
GameType type = Friends.list[self->num].game_invite.type;
uint32_t id = Friends.list[self->num].game_invite.id;
uint8_t *data = Friends.list[self->num].game_invite.data;
size_t length = Friends.list[self->num].game_invite.data_length;
int ret = game_initialize(self, m, type, id, data, length);
switch (ret) {
case 0: {
free(data);
Friends.list[self->num].game_invite.data = NULL;
Friends.list[self->num].game_invite.pending = false;
break;
}
case -1: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Window is too small. Try enlarging your window.");
return;
}
case -2: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (network error)");
return;
}
default: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (error %d)", ret);
return;
}
}
}
#endif // GAMES
void cmd_savefile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File ID required.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File ID required.");
return;
}
long int idx = strtol(argv[1], NULL, 10);
if ((idx == 0 && strcmp(argv[1], "0")) || idx < 0 || idx >= MAX_FILES) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending file transfers with that ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending file transfers with that ID.");
return;
}
struct FileTransfer *ft = get_file_transfer_struct_index(self->num, idx, FILE_TRANSFER_RECV);
if (!ft) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending file transfers with that ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending file transfers with that ID.");
return;
}
if (ft->state != FILE_TRANSFER_PENDING) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending file transfers with that ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending file transfers with that ID.");
return;
}
if ((ft->file = fopen(ft->file_path, "a")) == NULL) {
const char *msg = "File transfer failed: Invalid file path.";
const char *msg = "File transfer failed: Invalid download path.";
close_file_transfer(self, m, ft, TOX_FILE_CONTROL_CANCEL, msg, notif_error);
return;
}
TOX_ERR_FILE_CONTROL err;
tox_file_control(m, self->num, ft->filenum, TOX_FILE_CONTROL_RESUME, &err);
Tox_Err_File_Control err;
tox_file_control(m, self->num, ft->filenumber, TOX_FILE_CONTROL_RESUME, &err);
if (err != TOX_ERR_FILE_CONTROL_OK)
if (err != TOX_ERR_FILE_CONTROL_OK) {
goto on_recv_error;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Saving file [%d] as: '%s'", idx, ft->file_path);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Saving file [%ld] as: '%s'", idx, ft->file_path);
/* prep progress bar line */
char progline[MAX_STR_SIZE];
init_progress_bar(progline);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", progline);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", progline);
ft->line_id = self->chatwin->hst->line_end->id + 2;
ft->state = FILE_TRANSFER_STARTED;
@ -194,63 +280,58 @@ on_recv_error:
switch (err) {
case TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND:
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed: Friend not found.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed: Friend not found.");
return;
case TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED:
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed: Friend is not online.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed: Friend is not online.");
return;
case TOX_ERR_FILE_CONTROL_NOT_FOUND:
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed: Invalid filenumber.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed: Invalid filenumber.");
return;
case TOX_ERR_FILE_CONTROL_SENDQ:
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed: Connection error.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed: Connection error.");
return;
default:
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed (error %d)\n", err);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File transfer failed (error %d)\n", err);
return;
}
}
void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
const char *errmsg = NULL;
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File path required.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File path required.");
return;
}
if (argv[1][0] != '\"') {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File path must be enclosed in quotes.");
return;
}
/* remove opening and closing quotes */
char path[MAX_STR_SIZE];
snprintf(path, sizeof(path), "%s", &argv[1][1]);
int path_len = strlen(path) - 1;
path[path_len] = '\0';
snprintf(path, sizeof(path), "%s", argv[1]);
int path_len = strlen(path);
if (path_len >= MAX_STR_SIZE) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File path exceeds character limit.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File path exceeds character limit.");
return;
}
FILE *file_to_send = fopen(path, "r");
if (file_to_send == NULL) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File not found.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File not found.");
return;
}
off_t filesize = file_size(path);
if (filesize == 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid file.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid file.");
fclose(file_to_send);
return;
}
@ -258,12 +339,13 @@ void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv
char file_name[TOX_MAX_FILENAME_LENGTH];
size_t namelen = get_file_name(file_name, sizeof(file_name), path);
TOX_ERR_FILE_SEND err;
Tox_Err_File_Send err;
uint32_t filenum = tox_file_send(m, self->num, TOX_FILE_KIND_DATA, (uint64_t) filesize, NULL,
(uint8_t *) file_name, namelen, &err);
if (err != TOX_ERR_FILE_SEND_OK)
if (err != TOX_ERR_FILE_SEND_OK) {
goto on_send_error;
}
struct FileTransfer *ft = new_file_transfer(self, self->num, filenum, FILE_TRANSFER_SEND, TOX_FILE_KIND_DATA);
@ -279,7 +361,7 @@ void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv
char sizestr[32];
bytes_convert_str(sizestr, sizeof(sizestr), filesize);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Sending file [%d]: '%s' (%s)", filenum, file_name, sizestr);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Sending file [%d]: '%s' (%s)", filenum, file_name, sizestr);
return;
@ -307,7 +389,7 @@ on_send_error:
break;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", errmsg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", errmsg);
tox_file_control(m, self->num, filenum, TOX_FILE_CONTROL_CANCEL, NULL);
fclose(file_to_send);
}

View File

@ -23,12 +23,13 @@
#ifndef CHAT_COMMANDS_H
#define CHAT_COMMANDS_H
#include "windows.h"
#include "toxic.h"
#include "windows.h"
void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_groupinvite(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_join_group(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference_invite(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference_join(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_game_join(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_savefile(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_sendfile(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
@ -41,11 +42,13 @@ void cmd_cancel(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZ
void cmd_ccur_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_mute(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_sense(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_bitrate(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
#endif /* AUDIO */
#ifdef VIDEO
void cmd_vcall(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_video(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_ccur_video_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_res(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
#endif /* VIDEO */
#endif /* #define CHAT_COMMANDS_H */
#endif /* CHAT_COMMANDS_H */

1421
src/conference.c Normal file

File diff suppressed because it is too large Load Diff

118
src/conference.h Normal file
View File

@ -0,0 +1,118 @@
/* conference.h
*
*
* Copyright (C) 2014 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CONFERENCE_H
#define CONFERENCE_H
#include "toxic.h"
#include "windows.h"
#define CONFERENCE_MAX_TITLE_LENGTH TOX_MAX_NAME_LENGTH
#define SIDEBAR_WIDTH 16
typedef struct ConferencePeer {
bool active;
uint8_t pubkey[TOX_PUBLIC_KEY_SIZE];
uint32_t peernum; /* index in chat->peer_list */
char name[TOX_MAX_NAME_LENGTH];
size_t name_length;
bool sending_audio;
uint32_t audio_out_idx;
time_t last_audio_time;
} ConferencePeer;
typedef struct AudioInputCallbackData {
Tox *tox;
uint32_t conferencenum;
} AudioInputCallbackData;
#define PUBKEY_STRING_SIZE (2 * TOX_PUBLIC_KEY_SIZE + 1)
typedef struct NameListEntry {
char name[TOX_MAX_NAME_LENGTH];
char pubkey_str[PUBKEY_STRING_SIZE];
uint32_t peernum;
} NameListEntry;
typedef struct {
int chatwin;
bool active;
uint8_t type;
int side_pos; /* current position of the sidebar - used for scrolling up and down */
time_t start_time;
char title[CONFERENCE_MAX_TITLE_LENGTH + 1];
size_t title_length;
ConferencePeer *peer_list;
uint32_t max_idx;
NameListEntry *name_list;
uint32_t num_peers;
bool push_to_talk_enabled;
time_t ptt_last_pushed;
bool audio_enabled;
time_t last_sent_audio;
uint32_t audio_in_idx;
AudioInputCallbackData audio_input_callback_data;
} ConferenceChat;
/* Frees all Toxic associated data structures for a conference (does not call tox_conference_delete() ) */
void free_conference(ToxWindow *self, uint32_t conferencenum);
int init_conference_win(Tox *m, uint32_t conferencenum, uint8_t type, const char *title, size_t length);
/* destroys and re-creates conference window with or without the peerlist */
void redraw_conference_win(ToxWindow *self);
void conference_set_title(ToxWindow *self, uint32_t conferencesnum, const char *title, size_t length);
void conference_rename_log_path(Tox *m, uint32_t conferencenum, const char *new_title);
int conference_enable_logging(ToxWindow *self, Tox *m, uint32_t conferencenum, struct chatlog *log);
/* Puts `(NameListEntry *)`s in `entries` for each matched peer, up to a maximum
* of `maxpeers`.
* Maches each peer whose name or pubkey begins with `prefix`.
* If `prefix` is exactly the pubkey of a peer, matches only that peer.
* return number of entries placed in `entries`.
*/
uint32_t get_name_list_entries_by_prefix(uint32_t conferencenum, const char *prefix, NameListEntry **entries,
uint32_t maxpeers);
bool init_conference_audio_input(Tox *tox, uint32_t conferencenum);
bool enable_conference_audio(Tox *tox, uint32_t conferencenum);
bool disable_conference_audio(Tox *tox, uint32_t conferencenum);
bool toggle_conference_push_to_talk(uint32_t conferencenum, bool enabled);
void audio_conference_callback(void *tox, uint32_t conferencenum, uint32_t peernum,
const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t
sample_rate, void *userdata);
bool conference_mute_self(uint32_t conferencenum);
bool conference_mute_peer(const Tox *m, uint32_t conferencenum, uint32_t peernum);
bool conference_set_VAD_threshold(uint32_t conferencenum, float threshold);
float conference_get_VAD_threshold(uint32_t conferencenum);
#endif /* CONFERENCE_H */

210
src/conference_commands.c Normal file
View File

@ -0,0 +1,210 @@
/* conference_commands.c
*
*
* Copyright (C) 2014 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <string.h>
#include "conference.h"
#include "line_info.h"
#include "log.h"
#include "misc_tools.h"
#include "toxic.h"
#include "windows.h"
static void print_err(ToxWindow *self, const char *error_str)
{
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", error_str);
}
void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
Tox_Err_Conference_Title err;
char title[CONFERENCE_MAX_TITLE_LENGTH + 1];
if (argc < 1) {
size_t tlen = tox_conference_get_title_size(m, self->num, &err);
if (err != TOX_ERR_CONFERENCE_TITLE_OK || tlen >= sizeof(title)) {
print_err(self, "Title is not set");
return;
}
if (!tox_conference_get_title(m, self->num, (uint8_t *) title, &err)) {
print_err(self, "Title is not set");
return;
}
title[tlen] = '\0';
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Title is set to: %s", title);
return;
}
size_t len = strlen(argv[1]);
if (len >= sizeof(title)) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to set title: max length exceeded.");
return;
}
snprintf(title, sizeof(title), "%s", argv[1]);
if (!tox_conference_set_title(m, self->num, (uint8_t *) title, len, &err)) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to set title (error %d)", err);
return;
}
conference_rename_log_path(m, self->num, title); // must be called first
conference_set_title(self, self->num, title, len);
char selfnick[TOX_MAX_NAME_LENGTH];
tox_self_get_name(m, (uint8_t *) selfnick);
size_t sn_len = tox_self_get_name_size(m);
selfnick[sn_len] = '\0';
line_info_add(self, true, selfnick, NULL, NAME_CHANGE, 0, 0, " set the conference title to: %s", title);
char tmp_event[MAX_STR_SIZE + 20];
snprintf(tmp_event, sizeof(tmp_event), "set title to %s", title);
write_to_log(tmp_event, selfnick, self->chatwin->log, true);
}
#ifdef AUDIO
void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
bool enable;
if (argc == 1 && !strcasecmp(argv[1], "on")) {
enable = true;
} else if (argc == 1 && !strcasecmp(argv[1], "off")) {
enable = false;
} else {
print_err(self, "Please specify: on | off");
return;
}
if (enable ? enable_conference_audio(m, self->num) : disable_conference_audio(m, self->num)) {
print_err(self, enable ? "Enabled conference audio. Use the '/ptt' command to toggle Push-To-Talk."
: "Disabled conference audio");
} else {
print_err(self, enable ? "Failed to enable audio" : "Failed to disable audio");
}
}
void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc < 1) {
if (conference_mute_self(self->num)) {
print_err(self, "Toggled self audio mute status");
} else {
print_err(self, "No audio input to mute");
}
} else {
NameListEntry *entries[16];
uint32_t n = get_name_list_entries_by_prefix(self->num, argv[1], entries, 16);
if (n == 0) {
print_err(self, "No such peer");
return;
}
if (n > 1) {
print_err(self, "Multiple matching peers (use /mute [public key] to disambiguate):");
for (uint32_t i = 0; i < n; ++i) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s: %s", entries[i]->pubkey_str, entries[i]->name);
}
return;
}
if (conference_mute_peer(m, self->num, entries[0]->peernum)) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Toggled audio mute status of %s", entries[0]->name);
} else {
print_err(self, "Peer is not on the call");
}
}
}
void cmd_conference_sense(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(m);
if (argc == 0) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Current VAD threshold: %.1f",
(double) conference_get_VAD_threshold(self->num));
return;
}
if (argc > 1) {
print_err(self, "Only one argument allowed.");
return;
}
char *end;
float value = strtof(argv[1], &end);
if (*end) {
print_err(self, "Invalid input");
return;
}
if (conference_set_VAD_threshold(self->num, value)) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Set VAD threshold to %.1f", (double) value);
} else {
print_err(self, "Failed to set conference audio input sensitivity.");
}
}
void cmd_conference_push_to_talk(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(m);
bool enable;
if (argc == 1 && !strcasecmp(argv[1], "on")) {
enable = true;
} else if (argc == 1 && !strcasecmp(argv[1], "off")) {
enable = false;
} else {
print_err(self, "Please specify: on | off");
return;
}
if (!toggle_conference_push_to_talk(self->num, enable)) {
print_err(self, "Failed to toggle push to talk.");
return;
}
print_err(self, enable ? "Push-To-Talk is enabled. Push F2 to activate" : "Push-To-Talk is disabled");
}
#endif /* AUDIO */

35
src/conference_commands.h Normal file
View File

@ -0,0 +1,35 @@
/* conference_commands.h
*
*
* Copyright (C) 2014 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CONFERENCE_COMMANDS_H
#define CONFERENCE_COMMANDS_H
#include "toxic.h"
#include "windows.h"
void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference_sense(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference_push_to_talk(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
#endif /* CONFERENCE_COMMANDS_H */

View File

@ -20,18 +20,18 @@
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "toxic.h"
#include "configdir.h"
#include "misc_tools.h"
#include "toxic.h"
/* get the user's home directory. */
void get_home_dir(char *home, int size)
@ -48,8 +48,9 @@ void get_home_dir(char *home, int size)
} else {
hmstr = getenv("HOME");
if (hmstr == NULL)
if (hmstr == NULL) {
return;
}
snprintf(buf, sizeof(buf), "%s", hmstr);
hmstr = buf;
@ -77,8 +78,9 @@ char *get_user_config_dir(void)
len = strlen(home) + strlen("/Library/Application Support") + 1;
user_config_dir = malloc(len);
if (user_config_dir == NULL)
if (user_config_dir == NULL) {
return NULL;
}
snprintf(user_config_dir, len, "%s/Library/Application Support", home);
# else /* __APPLE__ */
@ -89,8 +91,9 @@ char *get_user_config_dir(void)
len = strlen(home) + strlen("/.config") + 1;
user_config_dir = malloc(len);
if (user_config_dir == NULL)
if (user_config_dir == NULL) {
return NULL;
}
snprintf(user_config_dir, len, "%s/.config", home);
} else {
@ -112,14 +115,16 @@ int create_user_config_dirs(char *path)
struct stat buf;
int mkdir_err = mkdir(path, 0700);
if (mkdir_err && (errno != EEXIST || stat(path, &buf) || !S_ISDIR(buf.st_mode)))
if (mkdir_err && (errno != EEXIST || stat(path, &buf) || !S_ISDIR(buf.st_mode))) {
return -1;
}
char *fullpath = malloc(strlen(path) + strlen(CONFIGDIR) + 1);
char *logpath = malloc(strlen(path) + strlen(LOGDIR) + 1);
if (fullpath == NULL || logpath == NULL)
if (fullpath == NULL || logpath == NULL) {
exit_toxic_err("failed in load_data_structures", FATALERR_MEMORY);
}
strcpy(fullpath, path);
strcat(fullpath, CONFIGDIR);

View File

@ -53,4 +53,4 @@ void get_home_dir(char *home, int size);
*/
int create_user_config_dirs(char *path);
#endif /* #define CONFIGDIR_H */
#endif /* CONFIGDIR_H */

View File

@ -36,8 +36,9 @@
*/
int set_curl_proxy(CURL *c_handle, const char *proxy_address, uint16_t port, uint8_t proxy_type)
{
if (proxy_type == TOX_PROXY_TYPE_NONE)
if (proxy_type == TOX_PROXY_TYPE_NONE) {
return 0;
}
if (proxy_address == NULL || port == 0) {
return -1;

View File

@ -23,6 +23,8 @@
#ifndef CURL_UTIL_H
#define CURL_UTIL_H
#include <stdint.h>
/* List based on Mozilla's recommended configurations for modern browsers */
#define TLS_CIPHER_SUITE_LIST "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK"
@ -52,4 +54,4 @@ int set_curl_proxy(CURL *c_handle, const char *proxy_address, uint16_t port, uin
*/
size_t curl_cb_write_data(void *data, size_t size, size_t nmemb, void *user_pointer);
#endif /* CURL_UTIL_H */
#endif /* CURL_UTIL_H */

View File

@ -20,19 +20,20 @@
*
*/
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "toxic.h"
#include "windows.h"
#include "execute.h"
#include "api.h"
#include "chat_commands.h"
#include "execute.h"
#include "global_commands.h"
#include "group_commands.h"
#include "conference_commands.h"
#include "line_info.h"
#include "misc_tools.h"
#include "notify.h"
#include "toxic.h"
#include "windows.h"
struct cmd_func {
const char *name;
@ -47,11 +48,16 @@ static struct cmd_func global_commands[] = {
{ "/connect", cmd_connect },
{ "/decline", cmd_decline },
{ "/exit", cmd_quit },
{ "/group", cmd_groupchat },
{ "/conference", cmd_conference },
#ifdef GAMES
{ "/game", cmd_game },
#endif
{ "/help", cmd_prompt_help },
{ "/log", cmd_log },
{ "/myid", cmd_myid },
#ifdef QRCODE
{ "/myqr", cmd_myqr },
#endif /* QRCODE */
{ "/nick", cmd_nick },
{ "/note", cmd_note },
{ "/nospam", cmd_nospam },
@ -64,88 +70,152 @@ static struct cmd_func global_commands[] = {
{ "/sdev", cmd_change_device },
#endif /* AUDIO */
#ifdef VIDEO
{ "/lsvdev", cmd_list_video_devices },
{ "/svdev" , cmd_change_video_device },
{ "/lsvdev", cmd_list_video_devices },
{ "/svdev", cmd_change_video_device },
#endif /* VIDEO */
#ifdef PYTHON
{ "/run", cmd_run },
#endif /* PYTHON */
{ NULL, NULL },
};
static struct cmd_func chat_commands[] = {
{ "/cancel", cmd_cancelfile },
{ "/invite", cmd_groupinvite },
{ "/join", cmd_join_group },
{ "/savefile", cmd_savefile },
{ "/sendfile", cmd_sendfile },
{ "/cancel", cmd_cancelfile },
{ "/invite", cmd_conference_invite },
{ "/join", cmd_conference_join },
#ifdef GAMES
{ "/play", cmd_game_join },
#endif
{ "/savefile", cmd_savefile },
{ "/sendfile", cmd_sendfile },
#ifdef AUDIO
{ "/call", cmd_call },
{ "/answer", cmd_answer },
{ "/reject", cmd_reject },
{ "/hangup", cmd_hangup },
{ "/mute", cmd_mute },
{ "/sense", cmd_sense },
{ "/call", cmd_call },
{ "/answer", cmd_answer },
{ "/reject", cmd_reject },
{ "/hangup", cmd_hangup },
{ "/mute", cmd_mute },
{ "/sense", cmd_sense },
{ "/bitrate", cmd_bitrate },
#endif /* AUDIO */
#ifdef VIDEO
{ "/video", cmd_video },
{ "/vcall", cmd_vcall },
{ "/video", cmd_video },
{ "/res", cmd_res },
#endif /* VIDEO */
{ NULL, NULL },
{ NULL, NULL },
};
static struct cmd_func group_commands[] = {
{ "/title", cmd_set_title },
static struct cmd_func conference_commands[] = {
{ "/title", cmd_conference_set_title },
#ifdef AUDIO
{ "/mute", cmd_mute },
{ "/sense", cmd_sense },
{ "/audio", cmd_enable_audio },
{ "/mute", cmd_conference_mute },
{ "/ptt", cmd_conference_push_to_talk },
{ "/sense", cmd_conference_sense },
#endif /* AUDIO */
{ NULL, NULL },
{ NULL, NULL },
};
/* Parses input command and puts args into arg array.
Returns number of arguments on success, -1 on failure. */
static int parse_command(WINDOW *w, ToxWindow *self, const char *input, char (*args)[MAX_STR_SIZE])
#ifdef PYTHON
#define SPECIAL_COMMANDS 7
#else
#define SPECIAL_COMMANDS 6
#endif /* PYTHON */
/* Special commands are commands that only take one argument even if it contains spaces */
static const char special_commands[SPECIAL_COMMANDS][MAX_CMDNAME_SIZE] = {
"/avatar",
"/nick",
"/note",
#ifdef PYTHON
"/run",
#endif /* PYTHON */
"/sendfile",
"/title",
"/mute",
};
/* Returns true if input command is in the special_commands array. */
static bool is_special_command(const char *input)
{
const int s = char_find(0, input, ' ');
for (int i = 0; i < SPECIAL_COMMANDS; ++i) {
if (strncmp(input, special_commands[i], s) == 0) {
return true;
}
}
return false;
}
/* Parses commands in the special_commands array. Unlike parse_command, this function
* does not split the input string at spaces.
*
* Returns the number of arguments.
*/
static int parse_special_command(const char *input, char (*args)[MAX_STR_SIZE])
{
int len = strlen(input);
int s = char_find(0, input, ' ');
memcpy(args[0], input, s);
args[0][s++] = '\0'; // increment to remove space after "/command "
if (s >= len) {
return 1; // No additional args
}
memcpy(args[1], input + s, len - s);
args[1][len - s] = '\0';
return 2;
}
/* Parses input command and puts args into arg array.
*
* Returns the number of arguments.
*/
static int parse_command(const char *input, char (*args)[MAX_STR_SIZE])
{
if (is_special_command(input)) {
return parse_special_command(input, args);
}
char *cmd = strdup(input);
if (cmd == NULL)
if (cmd == NULL) {
exit_toxic_err("failed in parse_command", FATALERR_MEMORY);
}
int num_args = 0;
int i = 0; /* index of last char in an argument */
/* characters wrapped in double quotes count as one arg */
while (num_args < MAX_NUM_ARGS) {
int qt_ofst = 0; /* set to 1 to offset index for quote char at end of arg */
int i = char_find(0, cmd, ' '); // index of last char in an argument
memcpy(args[num_args], cmd, i);
args[num_args++][i] = '\0';
if (*cmd == '\"') {
qt_ofst = 1;
i = char_find(1, cmd, '\"');
if (cmd[i] == '\0') {
const char *errmsg = "Invalid argument. Did you forget a closing \"?";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, errmsg);
free(cmd);
return -1;
}
} else {
i = char_find(0, cmd, ' ');
}
memcpy(args[num_args], cmd, i + qt_ofst);
args[num_args++][i + qt_ofst] = '\0';
if (cmd[i] == '\0') /* no more args */
if (cmd[i] == '\0') { // no more args
break;
}
char tmp[MAX_STR_SIZE];
snprintf(tmp, sizeof(tmp), "%s", &cmd[i + 1]);
strcpy(cmd, tmp); /* tmp will always fit inside cmd */
strcpy(cmd, tmp); // tmp will always fit inside cmd
}
free(cmd);
return num_args;
}
/* Matches command to respective function. Returns 0 on match, 1 on no match */
/* Matches command to respective function.
*
* Returns 0 on match.
* Returns 1 on no match
*/
static int do_command(WINDOW *w, ToxWindow *self, Tox *m, int num_args, struct cmd_func *commands,
char (*args)[MAX_STR_SIZE])
{
@ -163,35 +233,49 @@ static int do_command(WINDOW *w, ToxWindow *self, Tox *m, int num_args, struct c
void execute(WINDOW *w, ToxWindow *self, Tox *m, const char *input, int mode)
{
if (string_is_empty(input))
if (string_is_empty(input)) {
return;
}
char args[MAX_NUM_ARGS][MAX_STR_SIZE];
int num_args = parse_command(w, self, input, args);
int num_args = parse_command(input, args);
if (num_args == -1)
if (num_args <= 0) {
return;
}
/* Try to match input command to command functions. If non-global command mode is specified,
try specified mode's commands first, then upon failure try global commands.
Note: Global commands must come last in case of duplicate command names */
* try specified mode's commands first, then upon failure try global commands.
*
* Note: Global commands must come last in case of duplicate command names
*/
switch (mode) {
case CHAT_COMMAND_MODE:
if (do_command(w, self, m, num_args, chat_commands, args) == 0)
if (do_command(w, self, m, num_args, chat_commands, args) == 0) {
return;
}
break;
case GROUPCHAT_COMMAND_MODE:
if (do_command(w, self, m, num_args, group_commands, args) == 0)
case CONFERENCE_COMMAND_MODE:
if (do_command(w, self, m, num_args, conference_commands, args) == 0) {
return;
}
break;
}
if (do_command(w, self, m, num_args, global_commands, args) == 0)
if (do_command(w, self, m, num_args, global_commands, args) == 0) {
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid command.");
#ifdef PYTHON
if (do_plugin_command(num_args, args) == 0) {
return;
}
#endif
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid command.");
}

View File

@ -31,9 +31,9 @@
enum {
GLOBAL_COMMAND_MODE,
CHAT_COMMAND_MODE,
GROUPCHAT_COMMAND_MODE,
CONFERENCE_COMMAND_MODE,
};
void execute(WINDOW *w, ToxWindow *self, Tox *m, const char *input, int mode);
#endif /* #define EXECUTE_H */
#endif /* EXECUTE_H */

View File

@ -20,23 +20,24 @@
*
*/
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "toxic.h"
#include "windows.h"
#include "friendlist.h"
#include "file_transfers.h"
#include "friendlist.h"
#include "line_info.h"
#include "misc_tools.h"
#include "notify.h"
#include "toxic.h"
#include "windows.h"
extern FriendsList Friends;
/* number of "#"'s in file transfer progress bar. Keep well below MAX_STR_SIZE */
#define NUM_PROG_MARKS 50
#define STR_BUF_SIZE 30
/* creates initial progress line that will be updated during file transfer.
Assumes progline has room for at least MAX_STR_SIZE bytes */
@ -45,8 +46,9 @@ void init_progress_bar(char *progline)
strcpy(progline, "0% [");
int i;
for (i = 0; i < NUM_PROG_MARKS; ++i)
for (i = 0; i < NUM_PROG_MARKS; ++i) {
strcat(progline, "-");
}
strcat(progline, "] 0.0 B/s");
}
@ -54,42 +56,58 @@ void init_progress_bar(char *progline)
/* prints a progress bar for file transfers. */
void print_progress_bar(ToxWindow *self, double bps, double pct_done, uint32_t line_id)
{
if (bps < 0 || pct_done < 0 || pct_done > 100)
if (bps < 0 || pct_done < 0 || pct_done > 100) {
return;
}
char pct_str[24];
char pct_str[STR_BUF_SIZE];
snprintf(pct_str, sizeof(pct_str), "%.1f%%", pct_done);
char bps_str[24];
char bps_str[STR_BUF_SIZE];
bytes_convert_str(bps_str, sizeof(bps_str), bps);
char prog_line[NUM_PROG_MARKS + 1] = {0};
char prog_line[NUM_PROG_MARKS + 1];
prog_line[0] = 0;
int n = pct_done / (100 / NUM_PROG_MARKS);
int i, j;
for (i = 0; i < n; ++i)
for (i = 0; i < n; ++i) {
strcat(prog_line, "=");
}
if (pct_done < 100)
if (pct_done < 100) {
strcpy(prog_line + n, ">");
}
for (j = i; j < NUM_PROG_MARKS - 1; ++j)
for (j = i; j < NUM_PROG_MARKS - 1; ++j) {
strcat(prog_line, "-");
}
char full_line[strlen(pct_str) + NUM_PROG_MARKS + strlen(bps_str) + 7];
snprintf(full_line, sizeof(full_line), "%s [%s] %s/s", pct_str, prog_line, bps_str);
size_t line_buf_size = strlen(pct_str) + NUM_PROG_MARKS + strlen(bps_str) + 7;
char *full_line = malloc(line_buf_size);
if (full_line == NULL) {
return;
}
snprintf(full_line, line_buf_size, "%s [%s] %s/s", pct_str, prog_line, bps_str);
line_info_set(self, line_id, full_line);
free(full_line);
}
static void refresh_progress_helper(ToxWindow *self, Tox *m, struct FileTransfer *ft)
static void refresh_progress_helper(ToxWindow *self, struct FileTransfer *ft)
{
if (ft->state == FILE_TRANSFER_INACTIVE)
if (ft->state == FILE_TRANSFER_INACTIVE) {
return;
}
/* Timeout must be set to 1 second to show correct bytes per second */
if (!timed_out(ft->last_line_progress, 1))
if (!timed_out(ft->last_line_progress, 1)) {
return;
}
double remain = ft->file_size - ft->position;
double pct_done = remain > 0 ? (1 - (remain / ft->file_size)) * 100 : 100;
@ -100,33 +118,38 @@ static void refresh_progress_helper(ToxWindow *self, Tox *m, struct FileTransfer
}
/* refreshes active file transfer status bars. */
void refresh_file_transfer_progress(ToxWindow *self, Tox *m, uint32_t friendnum)
void refresh_file_transfer_progress(ToxWindow *self, uint32_t friendnumber)
{
size_t i;
for (i = 0; i < MAX_FILES; ++i) {
refresh_progress_helper(self, m, &Friends.list[friendnum].file_receiver[i]);
refresh_progress_helper(self, m, &Friends.list[friendnum].file_sender[i]);
for (size_t i = 0; i < MAX_FILES; ++i) {
refresh_progress_helper(self, &Friends.list[friendnumber].file_receiver[i]);
refresh_progress_helper(self, &Friends.list[friendnumber].file_sender[i]);
}
}
/* Returns a pointer to friendnum's FileTransfer struct associated with filenum.
* Returns NULL if filenum is invalid.
*/
struct FileTransfer *get_file_transfer_struct(uint32_t friendnum, uint32_t filenum)
static void clear_file_transfer(struct FileTransfer *ft)
{
size_t i;
*ft = (struct FileTransfer) {
0
};
}
for (i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft_send = &Friends.list[friendnum].file_sender[i];
/* Returns a pointer to friendnumber's FileTransfer struct associated with filenumber.
* Returns NULL if filenumber is invalid.
*/
struct FileTransfer *get_file_transfer_struct(uint32_t friendnumber, uint32_t filenumber)
{
for (size_t i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft_send = &Friends.list[friendnumber].file_sender[i];
if (ft_send->state != FILE_TRANSFER_INACTIVE && ft_send->filenum == filenum)
if (ft_send->state != FILE_TRANSFER_INACTIVE && ft_send->filenumber == filenumber) {
return ft_send;
}
struct FileTransfer *ft_recv = &Friends.list[friendnum].file_receiver[i];
struct FileTransfer *ft_recv = &Friends.list[friendnumber].file_receiver[i];
if (ft_recv->state != FILE_TRANSFER_INACTIVE && ft_recv->filenum == filenum)
if (ft_recv->state != FILE_TRANSFER_INACTIVE && ft_recv->filenumber == filenumber) {
return ft_recv;
}
}
return NULL;
@ -135,21 +158,21 @@ struct FileTransfer *get_file_transfer_struct(uint32_t friendnum, uint32_t filen
/* Returns a pointer to the FileTransfer struct associated with index with the direction specified.
* Returns NULL on failure.
*/
struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnum, uint32_t index,
struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint32_t index,
FILE_TRANSFER_DIRECTION direction)
{
if (direction != FILE_TRANSFER_RECV && direction != FILE_TRANSFER_SEND)
if (direction != FILE_TRANSFER_RECV && direction != FILE_TRANSFER_SEND) {
return NULL;
}
size_t i;
for (i = 0; i < MAX_FILES; ++i) {
for (size_t i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = direction == FILE_TRANSFER_SEND ?
&Friends.list[friendnum].file_sender[i] :
&Friends.list[friendnum].file_receiver[i];
&Friends.list[friendnumber].file_sender[i] :
&Friends.list[friendnumber].file_receiver[i];
if (ft->state != FILE_TRANSFER_INACTIVE && ft->index == index)
if (ft->state != FILE_TRANSFER_INACTIVE && ft->index == index) {
return ft;
}
}
return NULL;
@ -158,23 +181,19 @@ struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnum, uint32_t
/* Returns a pointer to an unused file sender.
* Returns NULL if all file senders are in use.
*/
static struct FileTransfer *new_file_sender(ToxWindow *window, uint32_t friendnum, uint32_t filenum, uint8_t type)
static struct FileTransfer *new_file_sender(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber, uint8_t type)
{
size_t i;
for (i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = &Friends.list[friendnum].file_sender[i];
for (size_t i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = &Friends.list[friendnumber].file_sender[i];
if (ft->state == FILE_TRANSFER_INACTIVE) {
memset(ft, 0, sizeof(struct FileTransfer));
clear_file_transfer(ft);
ft->window = window;
ft->index = i;
ft->friendnum = friendnum;
ft->filenum = filenum;
ft->friendnumber = friendnumber;
ft->filenumber = filenumber;
ft->file_type = type;
ft->last_keep_alive = get_unix_time();
ft->state = FILE_TRANSFER_PENDING;
ft->direction = FILE_TRANSFER_SEND;
return ft;
}
}
@ -185,23 +204,20 @@ static struct FileTransfer *new_file_sender(ToxWindow *window, uint32_t friendnu
/* Returns a pointer to an unused file receiver.
* Returns NULL if all file receivers are in use.
*/
static struct FileTransfer *new_file_receiver(ToxWindow *window, uint32_t friendnum, uint32_t filenum, uint8_t type)
static struct FileTransfer *new_file_receiver(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber,
uint8_t type)
{
size_t i;
for (i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = &Friends.list[friendnum].file_receiver[i];
for (size_t i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = &Friends.list[friendnumber].file_receiver[i];
if (ft->state == FILE_TRANSFER_INACTIVE) {
memset(ft, 0, sizeof(struct FileTransfer));
clear_file_transfer(ft);
ft->window = window;
ft->index = i;
ft->friendnum = friendnum;
ft->filenum = filenum;
ft->friendnumber = friendnumber;
ft->filenumber = filenumber;
ft->file_type = type;
ft->last_keep_alive = get_unix_time();
ft->state = FILE_TRANSFER_PENDING;
ft->direction = FILE_TRANSFER_RECV;
return ft;
}
}
@ -212,14 +228,16 @@ static struct FileTransfer *new_file_receiver(ToxWindow *window, uint32_t friend
/* Initializes an unused file transfer and returns its pointer.
* Returns NULL on failure.
*/
struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnum, uint32_t filenum,
struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber,
FILE_TRANSFER_DIRECTION direction, uint8_t type)
{
if (direction == FILE_TRANSFER_RECV)
return new_file_receiver(window, friendnum, filenum, type);
if (direction == FILE_TRANSFER_RECV) {
return new_file_receiver(window, friendnumber, filenumber, type);
}
if (direction == FILE_TRANSFER_SEND)
return new_file_sender(window, friendnum, filenum, type);
if (direction == FILE_TRANSFER_SEND) {
return new_file_sender(window, friendnumber, filenumber, type);
}
return NULL;
}
@ -233,45 +251,59 @@ struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnum, ui
void close_file_transfer(ToxWindow *self, Tox *m, struct FileTransfer *ft, int CTRL, const char *message,
Notification sound_type)
{
if (!ft)
if (!ft) {
return;
if (ft->state == FILE_TRANSFER_INACTIVE)
return;
if (ft->file)
fclose(ft->file);
if (CTRL >= 0)
tox_file_control(m, ft->friendnum, ft->filenum, (TOX_FILE_CONTROL) CTRL, NULL);
if (message && self) {
if (self->active_box != -1 && sound_type != silent)
box_notify2(self, sound_type, NT_NOFOCUS | NT_WNDALERT_2, self->active_box, "%s", message);
else
box_notify(self, sound_type, NT_NOFOCUS | NT_WNDALERT_2, &self->active_box, self->name, "%s", message);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", message);
}
memset(ft, 0, sizeof(struct FileTransfer));
if (ft->state == FILE_TRANSFER_INACTIVE) {
return;
}
if (ft->file) {
fclose(ft->file);
}
if (CTRL >= 0) {
tox_file_control(m, ft->friendnumber, ft->filenumber, (Tox_File_Control) CTRL, NULL);
}
if (message && self) {
if (self->active_box != -1 && sound_type != silent) {
box_notify2(self, sound_type, NT_NOFOCUS | NT_WNDALERT_2, self->active_box, "%s", message);
} else {
box_notify(self, sound_type, NT_NOFOCUS | NT_WNDALERT_2, &self->active_box, self->name, "%s", message);
}
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", message);
}
clear_file_transfer(ft);
}
/* Kills all active file transfers for friendnum */
void kill_all_file_transfers_friend(Tox *m, uint32_t friendnum)
/* Kills active outgoing avatar file transfers for friendnumber */
void kill_avatar_file_transfers_friend(Tox *m, uint32_t friendnumber)
{
size_t i;
for (size_t i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = &Friends.list[friendnumber].file_sender[i];
for (i = 0; i < MAX_FILES; ++i) {
close_file_transfer(NULL, m, &Friends.list[friendnum].file_sender[i], TOX_FILE_CONTROL_CANCEL, NULL, silent);
close_file_transfer(NULL, m, &Friends.list[friendnum].file_receiver[i], TOX_FILE_CONTROL_CANCEL, NULL, silent);
if (ft->file_type == TOX_FILE_KIND_AVATAR) {
close_file_transfer(NULL, m, ft, TOX_FILE_CONTROL_CANCEL, NULL, silent);
}
}
}
/* Kills all active file transfers for friendnumber */
void kill_all_file_transfers_friend(Tox *m, uint32_t friendnumber)
{
for (size_t i = 0; i < MAX_FILES; ++i) {
close_file_transfer(NULL, m, &Friends.list[friendnumber].file_sender[i], TOX_FILE_CONTROL_CANCEL, NULL, silent);
close_file_transfer(NULL, m, &Friends.list[friendnumber].file_receiver[i], TOX_FILE_CONTROL_CANCEL, NULL, silent);
}
}
void kill_all_file_transfers(Tox *m)
{
size_t i;
for (i = 0; i < Friends.max_idx; ++i)
for (size_t i = 0; i < Friends.max_idx; ++i) {
kill_all_file_transfers_friend(m, Friends.list[i].num);
}
}

View File

@ -25,9 +25,9 @@
#include <limits.h>
#include "notify.h"
#include "toxic.h"
#include "windows.h"
#include "notify.h"
#define KiB 1024
#define MiB 1048576 /* 1024^2 */
@ -51,18 +51,16 @@ struct FileTransfer {
ToxWindow *window;
FILE *file;
FILE_TRANSFER_STATE state;
FILE_TRANSFER_DIRECTION direction;
uint8_t file_type;
char file_name[TOX_MAX_FILENAME_LENGTH + 1];
char file_path[PATH_MAX + 1]; /* Not used by senders */
double bps;
uint32_t filenum;
uint32_t friendnum;
uint32_t filenumber;
uint32_t friendnumber;
size_t index;
uint64_t file_size;
uint64_t position;
time_t last_line_progress; /* The last time we updated the progress bar */
time_t last_keep_alive; /* The last time we sent or received data */
uint32_t line_id;
uint8_t file_id[TOX_FILE_ID_LENGTH];
};
@ -75,24 +73,24 @@ void init_progress_bar(char *progline);
void print_progress_bar(ToxWindow *self, double pct_done, double bps, uint32_t line_id);
/* refreshes active file transfer status bars. */
void refresh_file_transfer_progress(ToxWindow *self, Tox *m, uint32_t friendnum);
void refresh_file_transfer_progress(ToxWindow *self, uint32_t friendnumber);
/* Returns a pointer to friendnum's FileTransfer struct associated with filenum.
* Returns NULL if filenum is invalid.
/* Returns a pointer to friendnumber's FileTransfer struct associated with filenumber.
* Returns NULL if filenumber is invalid.
*/
struct FileTransfer *get_file_transfer_struct(uint32_t friendnum, uint32_t filenum);
struct FileTransfer *get_file_transfer_struct(uint32_t friendnumber, uint32_t filenumber);
/* Returns a pointer to the FileTransfer struct associated with index with the direction specified.
* Returns NULL on failure.
*/
struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnum, uint32_t index,
struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint32_t index,
FILE_TRANSFER_DIRECTION direction);
/* Initializes an unused file transfer and returns its pointer.
* Returns NULL on failure.
*/
struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnum, uint32_t filenum,
struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber,
FILE_TRANSFER_DIRECTION direction, uint8_t type);
/* Closes file transfer ft.
@ -103,9 +101,12 @@ struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnum, ui
void close_file_transfer(ToxWindow *self, Tox *m, struct FileTransfer *ft, int CTRL, const char *message,
Notification sound_type);
/* Kills all active file transfers for friendnum */
void kill_all_file_transfers_friend(Tox *m, uint32_t friendnum);
/* Kills active outgoing avatar file transfers for friendnumber */
void kill_avatar_file_transfers_friend(Tox *m, uint32_t friendnumber);
/* Kills all active file transfers for friendnumber */
void kill_all_file_transfers_friend(Tox *m, uint32_t friendnumber);
void kill_all_file_transfers(Tox *m);
#endif /* #define FILE_TRANSFERS_H */
#endif /* FILE_TRANSFERS_H */

File diff suppressed because it is too large Load Diff

View File

@ -25,9 +25,13 @@
#include <time.h>
#include "file_transfers.h"
#include "toxic.h"
#include "windows.h"
#include "file_transfers.h"
#ifdef GAMES
#include "game_base.h"
#endif
struct LastOnline {
uint64_t last_on;
@ -35,29 +39,45 @@ struct LastOnline {
char hour_min_str[TIME_STR_SIZE]; /* holds 12/24-hour time string e.g. "10:43 PM" */
};
struct GroupChatInvite {
struct ConferenceInvite {
char *key;
uint16_t length;
uint8_t type;
bool pending;
};
#ifdef GAMES
struct GameInvite {
uint8_t *data;
size_t data_length;
GameType type;
uint32_t id;
bool pending;
};
#endif // GAMES
typedef struct {
char name[TOXIC_MAX_NAME_LENGTH + 1];
int namelength;
uint16_t namelength;
char statusmsg[TOX_MAX_STATUS_MESSAGE_LENGTH + 1];
size_t statusmsg_len;
char pub_key[TOX_PUBLIC_KEY_SIZE];
uint32_t num;
int chatwin;
bool active;
TOX_CONNECTION connection_status;
Tox_Connection connection_status;
bool is_typing;
bool logging_on; /* saves preference for friend irrespective of global settings */
uint8_t status;
Tox_User_Status status;
struct LastOnline last_online;
struct GroupChatInvite group_invite;
struct ConferenceInvite conference_invite;
#ifdef GAMES
struct GameInvite game_invite;
#endif
struct FileTransfer file_receiver[MAX_FILES];
struct FileTransfer file_sender[MAX_FILES];
@ -65,7 +85,7 @@ typedef struct {
typedef struct {
char name[TOXIC_MAX_NAME_LENGTH + 1];
int namelength;
uint16_t namelength;
char pub_key[TOX_PUBLIC_KEY_SIZE];
uint32_t num;
bool active;
@ -81,14 +101,24 @@ typedef struct {
ToxicFriend *list;
} FriendsList;
ToxWindow new_friendlist(void);
ToxWindow *new_friendlist(void);
void friendlist_onInit(ToxWindow *self, Tox *m);
void disable_chatwin(uint32_t f_num);
int get_friendnum(uint8_t *name);
int load_blocklist(char *data);
void kill_friendlist(void);
void kill_friendlist(ToxWindow *self);
void friendlist_onFriendAdded(ToxWindow *self, Tox *m, uint32_t num, bool sort);
Tox_User_Status get_friend_status(uint32_t friendnumber);
Tox_Connection get_friend_connection_status(uint32_t friendnumber);
/* sorts friendlist_index first by connection status then alphabetically */
void sort_friendlist_index(void);
/*
* Returns true if friend associated with `public_key` is in the block list.
*
* `public_key` must be at least TOX_PUBLIC_KEY_SIZE bytes.
*/
bool friend_is_blocked(const char *public_key);
#endif /* end of include guard: FRIENDLIST_H */

1134
src/game_base.c Normal file

File diff suppressed because it is too large Load Diff

400
src/game_base.h Normal file
View File

@ -0,0 +1,400 @@
/* game_base.h
*
*
* Copyright (C) 2020 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GAME_BASE
#define GAME_BASE
#include <ncurses.h>
#include <time.h>
#include <tox/tox.h>
#include "game_util.h"
#include "windows.h"
#define GAME_BORDER_COLOUR BAR_SOLID
/* Max size of a default square game window */
#define GAME_MAX_SQUARE_Y_DEFAULT 26
#define GAME_MAX_SQUARE_X_DEFAULT (GAME_MAX_SQUARE_Y_DEFAULT * 2)
/* Max size of a large square game window */
#define GAME_MAX_SQUARE_Y_LARGE 52
#define GAME_MAX_SQUARE_X_LARGE (GAME_MAX_SQUARE_Y_LARGE * 2)
/* Max size of a default size rectangle game window */
#define GAME_MAX_RECT_Y_DEFAULT 24
#define GAME_MAX_RECT_X_DEFAULT (GAME_MAX_RECT_Y_DEFAULT * 4)
/* Max size of a large rectangle game window */
#define GAME_MAX_RECT_Y_LARGE 52
#define GAME_MAX_RECT_X_LARGE (GAME_MAX_RECT_Y_LARGE * 4)
/* Maximum length of a game message set with game_set_message() */
#define GAME_MAX_MESSAGE_SIZE 64
/* Default number of seconds a game message is displayed for */
#define GAME_MESSAGE_DEFAULT_TIMEOUT 3
/***** START NETWORKING CONSTANTS *****/
/* Header starts after custom packet type byte. Comprised of: NetworkVersion (1b) + GameType (1b) + id (4b) */
#define GAME_PACKET_HEADER_SIZE (1 + 1 + sizeof(uint32_t))
/* Max size of a game packet including the header */
#define GAME_MAX_PACKET_SIZE 1024
/* Max size of a game packet payload */
#define GAME_MAX_DATA_SIZE (GAME_MAX_PACKET_SIZE - GAME_PACKET_HEADER_SIZE - 1)
/* Current version of networking protocol */
#define GAME_NETWORKING_VERSION 0x01
/***** END NETWORKING CONSTANTS *****/
typedef void cb_game_update_state(GameData *game, void *cb_data);
typedef void cb_game_render_window(GameData *game, WINDOW *window, void *cb_data);
typedef void cb_game_kill(GameData *game, void *cb_data);
typedef void cb_game_pause(GameData *game, bool is_paused, void *cb_data);
typedef void cb_game_key_press(GameData *game, int key, void *cb_data);
typedef void cb_game_on_packet(GameData *game, const uint8_t *data, size_t length, void *cb_data);
typedef enum GamePacketType {
GP_Invite = 0u,
GP_Data,
} GamePacketType;
typedef enum GameWindowShape {
GW_ShapeSquare = 0u,
GW_ShapeSquareLarge,
GW_ShapeRectangle,
GW_ShapeRectangleLarge,
GW_ShapeInvalid,
} GameWindowShape;
typedef enum GameStatus {
GS_None = 0u,
GS_Paused,
GS_Running,
GS_Finished,
GS_Invalid,
} GameStatus;
typedef enum GameType {
GT_Centipede = 0u,
GT_Chess,
GT_Life,
GT_Snake,
GT_Invalid,
} GameType;
typedef struct GameMessage {
char message[GAME_MAX_MESSAGE_SIZE + 1];
size_t length;
const Coords *coords; // pointer to coords so we can track movement
Coords original_coords; // static coords at time of being set
time_t timeout;
time_t set_time;
int attributes;
int colour;
Direction direction;
bool sticky;
bool priority;
} GameMessage;
struct GameData {
TIME_MS last_frame_time;
TIME_MS update_interval; // determines the refresh rate (lower means faster)
long int score;
size_t high_score;
int lives;
size_t level;
GameStatus status;
GameType type;
bool is_multiplayer;
bool show_lives;
bool show_score;
bool show_high_score;
bool show_level;
GameMessage *messages;
size_t messages_size;
int game_max_x; // max dimensions of game window
int game_max_y;
int parent_max_x; // max dimensions of parent window
int parent_max_y;
int window_id;
WINDOW *window;
Tox *tox; // must be locked with Winthread mutex
GameWindowShape window_shape;
uint32_t id; // indentifies multiplayer instance
uint32_t friend_number; // friendnumber associated with parent window
cb_game_update_state *cb_game_update_state;
void *cb_game_update_state_data;
cb_game_render_window *cb_game_render_window;
void *cb_game_render_window_data;
cb_game_kill *cb_game_kill;
void *cb_game_kill_data;
cb_game_pause *cb_game_pause;
void *cb_game_pause_data;
cb_game_key_press *cb_game_key_press;
void *cb_game_key_press_data;
cb_game_on_packet *cb_game_on_packet;
void *cb_game_on_packet_data;
};
/*
* Sets the callback for game state updates.
*/
void game_set_cb_update_state(GameData *game, cb_game_update_state *func, void *cb_data);
/*
* Sets the callback for frame rendering.
*/
void game_set_cb_render_window(GameData *game, cb_game_render_window *func, void *cb_data);
/*
* Sets the callback for game termination.
*/
void game_set_cb_kill(GameData *game, cb_game_kill *func, void *cb_data);
/*
* Sets the callback for the game pause event.
*/
void game_set_cb_on_pause(GameData *game, cb_game_pause *func, void *cb_data);
/*
* Sets the callback for the key press event.
*/
void game_set_cb_on_keypress(GameData *game, cb_game_key_press *func, void *cb_data);
/*
* Sets the callback for the game packet event.
*/
void game_set_cb_on_packet(GameData *game, cb_game_on_packet *func, void *cb_data);
/*
* Initializes game instance.
*
* `type` must be a valid GameType.
*
* `id` should be a unique integer to indentify the game instance. If we're being invited to a game
* this identifier should be sent via the invite packet.
*
* if `multiplayer_data` is non-null this indicates that we accepted a game invite from a contact.
* The data contains any information we need to initialize the game state.
*
* Return 0 on success.
* Return -1 if screen is too small.
* Return -2 on network related error.
* Return -3 if multiplayer game is being initialized outside of a contact's window.
* Return -4 on other failure.
*/
int game_initialize(const ToxWindow *self, Tox *m, GameType type, uint32_t id, const uint8_t *multiplayer_data,
size_t length);
/*
* Sets game window to `shape`.
*
* This must be called on game initialization.
*
* Return 0 on success.
* Return -1 if window is too small or shape is invalid.
* Return -2 if function is called while the game state is valid.
*/
int game_set_window_shape(GameData *game, GameWindowShape shape);
/*
* Returns the GameType associated with `game_string`.
*/
GameType game_get_type(const char *game_string);
/*
* Returns the name represented as a string associated with `type`.
*/
const char *game_get_name_string(GameType type);
/*
* Prints all available games to window associated with `self`.
*/
void game_list_print(ToxWindow *self);
/*
* Return true if game `type` has a multiplayer mode.
*/
bool game_type_is_multiplayer(GameType type);
/*
* Returns true if coordinates designated by `x` and `y` are within the game window boundaries.
*/
bool game_coordinates_in_bounds(const GameData *game, int x, int y);
/*
* Put random coordinates that fit within the game window in `coords`.
*/
void game_random_coords(const GameData *game, Coords *coords);
/*
* Gets the current max dimensions of the game window.
*/
void game_max_x_y(const GameData *game, int *x, int *y);
/*
* Returns the respective coordinate boundary of the game window.
*/
int game_y_bottom_bound(const GameData *game);
int game_y_top_bound(const GameData *game);
int game_x_right_bound(const GameData *game);
int game_x_left_bound(const GameData *game);
/*
* Toggle whether the respective game info is shown around the game window.
*/
void game_show_score(GameData *game, bool show_score);
void game_show_high_score(GameData *game, bool show_high_score);
void game_show_lives(GameData *game, bool show_lives);
void game_show_level(GameData *game, bool show_level);
/*
* Sends a notification to the window associated with `game`.
*
* `message` - the notification message that will be displayed.
*/
void game_window_notify(const GameData *game, const char *message);
/*
* Updates game score.
*/
void game_update_score(GameData *game, long int points);
/*
* Sets game score to `val`.
*/
void game_set_score(GameData *game, long int score);
/*
* Returns the game's current score.
*/
long int game_get_score(const GameData *game);
/*
* Increments level.
*
* This function should be called on initialization if game wishes to display level.
*/
void game_increment_level(GameData *game);
/*
* Updates lives with `amount`.
*
* If lives becomes negative the lives counter will not be drawn.
*/
void game_update_lives(GameData *game, int amount);
/*
* Returns the remaining number of lives for the game.
*/
int game_get_lives(const GameData *game);
/*
* Returns the current level.
*/
size_t game_get_current_level(const GameData *game);
/*
* Sets the game status to `status`.
*/
void game_set_status(GameData *game, GameStatus status);
/*
* Sets the game base update interval.
*
* Lower values of `update_interval` make the game faster, where 1 is the fastest and 50 is slowest.
* If this function is never called the game chooses a reasonable default.
*/
void game_set_update_interval(GameData *game, TIME_MS update_interval);
/*
* Creates a message `message` of size `length` to be displayed at `coords` for `timeout` seconds.
*
* Message must be no greater than GAME_MAX_MESSAGE_SIZE bytes in length.
*
* If `sticky` is true the message will follow coords if they move.
*
* If `dir` is a valid direction, the message will be positioned a few squares away from `coords`
* so as to not overlap with its associated object.
*
* If `timeout` is zero, the default timeout value will be used.
*
* If `priority` true, messages will be printed on top of game objects.
*
* Return 0 on success.
* Return -1 on failure.
*/
int game_set_message(GameData *game, const char *message, size_t length, Direction dir, int attributes, int colour,
time_t timeout, const Coords *coords, bool sticky, bool priority);
/*
* Returns true if game should update an object's state according to its last moved time and current speed.
*
* This is used to independently control the speed of various game objects.
*/
bool game_do_object_state_update(const GameData *game, TIME_MS current_time, TIME_MS last_moved_time, TIME_MS speed);
/*
* Returns the current wall time in milliseconds.
*/
TIME_MS get_time_millis(void);
/*
* Ends game associated with `self` and cleans up.
*/
void game_kill(ToxWindow *self);
/*
* Sends a packet containing payload `data` of size `length` to the friendnumber associated with the game's
* parent window.
*
* `length` must not exceed GAME_MAX_DATA_SIZE bytes.
*
* `packet_type` should be GP_Invite for an invite packet or GP_Data for all other game data.
*/
int game_packet_send(const GameData *game, const uint8_t *data, size_t length, GamePacketType packet_type);
#endif // GAME_BASE

1750
src/game_centipede.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/* group_commands.h
/* game_centipede.h
*
*
* Copyright (C) 2014 Toxic All Rights Reserved.
* Copyright (C) 2020 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
@ -20,12 +20,11 @@
*
*/
#ifndef GROUP_COMMANDS_H
#define GROUP_COMMANDS_H
#ifndef GAME_CENTIPEDE
#define GAME_CENTIPEDE
#include "windows.h"
#include "toxic.h"
#include "game_base.h"
void cmd_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
int centipede_initialize(GameData *game);
#endif /* GROUP_COMMANDS_H */
#endif // GAME_CENTIPEDE

2158
src/game_chess.c Normal file

File diff suppressed because it is too large Load Diff

44
src/game_chess.h Normal file
View File

@ -0,0 +1,44 @@
/* game_chess.h
*
*
* Copyright (C) 2020 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GAME_CHESS
#define GAME_CHESS
#include "game_base.h"
/*
* Initializes chess game state.
*
* If `init_data` is non-null, this indicates that we were invited to the game.
*
* If we're the inviter, we send an invite packet after initialization. If we're the
* invitee, we send a handshake response packet to the inviter.
*
* Return 0 on success.
* Return -1 if window is too small.
* Return -2 on network related error.
* Return -3 on other error.
*/
int chess_initialize(GameData *game, const uint8_t *init_data, size_t length);
#endif // GAME_CHESS

676
src/game_life.c Normal file
View File

@ -0,0 +1,676 @@
/* game_life.c
*
*
* Copyright (C) 2021 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "game_life.h"
#define LIFE_DEFAULT_CELL_CHAR 'o'
#define LIFE_CELL_DEFAULT_COLOUR CYAN
#define LIFE_DEFAULT_SPEED 25
#define LIFE_MAX_SPEED 40
/* Determines the additional size of the grid beyond the visible boundaries.
*
* This buffer allows cells to continue growing off-screen giving the illusion of an
* infinite grid to a certain point.
*/
#define LIFE_BOUNDARY_BUFFER 50
typedef struct Cell {
Coords coords;
bool alive;
bool marked; // true if cell should invert alive status at end of current cycle
int display_char;
size_t age;
} Cell;
typedef struct LifeState {
TIME_MS time_last_cycle;
size_t speed;
size_t generation;
bool paused;
Cell **cells;
int num_columns;
int num_rows;
int curs_x;
int curs_y;
int x_left_bound;
int x_right_bound;
int y_top_bound;
int y_bottom_bound;
short display_candy;
int colour;
} LifeState;
static void life_increase_speed(LifeState *state)
{
if (state->speed < LIFE_MAX_SPEED) {
++state->speed;
}
}
static void life_decrease_speed(LifeState *state)
{
if (state->speed > 1) {
--state->speed;
}
}
static int life_get_display_char(const LifeState *state, const Cell *cell)
{
if (state->display_candy == 1) {
if (cell->age == 1) {
return '.';
}
return '+';
}
if (state->display_candy == 2) {
if (cell->age == 1) {
return '.';
}
if (cell->age == 2) {
return '-';
}
if (cell->age == 3) {
return 'o';
}
return 'O';
}
return 'o';
}
static void life_toggle_display_candy(LifeState *state)
{
state->display_candy = (state->display_candy + 1) % 3; // magic number depends on life_get_display_char()
}
static void life_cycle_colour(LifeState *state)
{
switch (state->colour) {
case RED: {
state->colour = YELLOW;
break;
}
case YELLOW: {
state->colour = GREEN;
break;
}
case GREEN: {
state->colour = CYAN;
break;
}
case CYAN: {
state->colour = BLUE;
break;
}
case BLUE: {
state->colour = MAGENTA;
break;
}
case MAGENTA: {
state->colour = RED;
break;
}
default: {
state->colour = RED;
break;
}
}
}
static Cell *life_get_cell_at_coords(const LifeState *state, const int x, const int y)
{
const int i = y - (state->y_top_bound - (LIFE_BOUNDARY_BUFFER / 2));
const int j = x - (state->x_left_bound - (LIFE_BOUNDARY_BUFFER / 2));
if (i >= 0 && j >= 0) {
return &state->cells[i][j];
}
return NULL;
}
static void life_draw_cells(const GameData *game, WINDOW *win, LifeState *state)
{
wattron(win, A_BOLD | COLOR_PAIR(state->colour));
for (int i = LIFE_BOUNDARY_BUFFER / 2; i < state->num_rows - (LIFE_BOUNDARY_BUFFER / 2); ++i) {
for (int j = LIFE_BOUNDARY_BUFFER / 2; j < state->num_columns + 1 - (LIFE_BOUNDARY_BUFFER / 2); ++j) {
Cell *cell = &state->cells[i][j];
if (cell->alive) {
Coords coords = cell->coords;
mvwaddch(win, coords.y, coords.x, cell->display_char);
}
}
}
wattroff(win, A_BOLD | COLOR_PAIR(state->colour));
}
static void life_toggle_cell(LifeState *state)
{
Cell *cell = life_get_cell_at_coords(state, state->curs_x, state->curs_y);
if (cell == NULL) {
return;
}
cell->alive ^= 1;
}
/*
* Returns the number of live neighbours of cell at `i` `j` position.
*
* Returns NULL if cell is touching a border.
*/
static int life_get_live_neighbours(const LifeState *state, const int i, const int j)
{
Cell *n[8] = {0};
if (i > 0 && j > 0) {
n[0] = &state->cells[i - 1][j - 1];
}
if (i > 0) {
n[1] = &state->cells[i - 1][j];
}
if (i > 0 && j < state->num_columns - 1) {
n[2] = &state->cells[i - 1][j + 1];
}
if (j > 0) {
n[3] = &state->cells[i][j - 1];
}
if (j < state->num_columns - 1) {
n[4] = &state->cells[i][j + 1];
}
if (i < state->num_rows - 1 && j > 0) {
n[5] = &state->cells[i + 1][j - 1];
}
if (i < state->num_rows - 1) {
n[6] = &state->cells[i + 1][j];
}
if (i < state->num_rows - 1 && j < state->num_columns - 1) {
n[7] = &state->cells[i + 1][j + 1];
}
int count = 0;
for (size_t i = 0; i < 8; ++i) {
if (n[i] == NULL) {
return 0; // If we're at a boundary kill cell
}
if (n[i]->alive) {
++count;
}
}
return count;
}
static void life_restart(GameData *game, LifeState *state)
{
for (int i = 0; i < state->num_rows; ++i) {
for (int j = 0; j < state->num_columns; ++j) {
Cell *cell = &state->cells[i][j];
cell->alive = false;
cell->marked = false;
cell->display_char = LIFE_DEFAULT_CELL_CHAR;
cell->age = 0;
}
}
game_set_score(game, 0);
state->generation = 0;
}
static void life_do_cells(LifeState *state)
{
for (int i = 0; i < state->num_rows; ++i) {
for (int j = 0; j < state->num_columns; ++j) {
Cell *cell = &state->cells[i][j];
if (cell->marked) {
cell->marked = false;
cell->alive ^= 1;
cell->age = cell->alive;
cell->display_char = life_get_display_char(state, cell);
} else if (cell->alive) {
++cell->age;
cell->display_char = life_get_display_char(state, cell);
}
}
}
}
static void life_cycle(GameData *game, LifeState *state)
{
if (state->generation == 0) {
return;
}
TIME_MS cur_time = get_time_millis();
if (!game_do_object_state_update(game, cur_time, state->time_last_cycle, state->speed)) {
return;
}
state->time_last_cycle = get_time_millis();
++state->generation;
size_t live_cells = 0;
for (int i = 0; i < state->num_rows; ++i) {
for (int j = 0; j < state->num_columns; ++j) {
Cell *cell = &state->cells[i][j];
int live_neighbours = life_get_live_neighbours(state, i, j);
if (cell->alive) {
if (!(live_neighbours == 2 || live_neighbours == 3)) {
cell->marked = true;
} else {
++live_cells;
}
} else {
if (live_neighbours == 3) {
cell->marked = true;
++live_cells;
}
}
}
}
if (live_cells == 0) {
life_restart(game, state);
return;
}
life_do_cells(state);
game_update_score(game, 1);
}
static void life_start(GameData *game, LifeState *state)
{
state->generation = 1;
}
void life_cb_update_game_state(GameData *game, void *cb_data)
{
if (!cb_data) {
return;
}
LifeState *state = (LifeState *)cb_data;
life_cycle(game, state);
}
void life_cb_render_window(GameData *game, WINDOW *win, void *cb_data)
{
if (!cb_data) {
return;
}
LifeState *state = (LifeState *)cb_data;
move(state->curs_y, state->curs_x);
if (state->generation == 0 || state->paused) {
curs_set(1);
}
life_draw_cells(game, win, state);
}
static void life_move_curs_left(LifeState *state)
{
int new_x = state->curs_x - 1;
if (new_x < state->x_left_bound) {
return;
}
state->curs_x = new_x;
}
static void life_move_curs_right(LifeState *state)
{
int new_x = state->curs_x + 1;
if (new_x > state->x_right_bound) {
return;
}
state->curs_x = new_x;
}
static void life_move_curs_up(LifeState *state)
{
int new_y = state->curs_y - 1;
if (new_y < state->y_top_bound) {
return;
}
state->curs_y = new_y;
}
static void life_move_curs_down(LifeState *state)
{
int new_y = state->curs_y + 1;
if (new_y >= state->y_bottom_bound) {
return;
}
state->curs_y = new_y;
}
static void life_move_curs_up_left(LifeState *state)
{
life_move_curs_up(state);
life_move_curs_left(state);
}
static void life_move_curs_up_right(LifeState *state)
{
life_move_curs_up(state);
life_move_curs_right(state);
}
static void life_move_curs_down_right(LifeState *state)
{
life_move_curs_down(state);
life_move_curs_right(state);
}
static void life_move_curs_down_left(LifeState *state)
{
life_move_curs_down(state);
life_move_curs_left(state);
}
void life_cb_on_keypress(GameData *game, int key, void *cb_data)
{
if (!cb_data) {
return;
}
LifeState *state = (LifeState *)cb_data;
switch (key) {
case KEY_LEFT: {
life_move_curs_left(state);
break;
}
case KEY_RIGHT: {
life_move_curs_right(state);
break;
}
case KEY_DOWN: {
life_move_curs_down(state);
break;
}
case KEY_UP: {
life_move_curs_up(state);
break;
}
case KEY_HOME: {
life_move_curs_up_left(state);
break;
}
case KEY_END: {
life_move_curs_down_left(state);
break;
}
case KEY_PPAGE: {
life_move_curs_up_right(state);
break;
}
case KEY_NPAGE: {
life_move_curs_down_right(state);
break;
}
case '\r': {
if (state->generation > 0) {
life_restart(game, state);
} else {
life_start(game, state);
}
break;
}
case ' ': {
life_toggle_cell(state);
break;
}
case '=':
/* intentional fallthrough */
case '+': {
life_increase_speed(state);
break;
}
case '-':
/* intentional fallthrough */
case '_': {
life_decrease_speed(state);
break;
}
case '\t': {
life_toggle_display_candy(state);
break;
}
case '`': {
life_cycle_colour(state);
break;
}
default: {
return;
}
}
}
static void life_free_cells(LifeState *state)
{
if (state->cells == NULL) {
return;
}
for (int i = 0; i < state->num_rows; ++i) {
if (state->cells[i]) {
free(state->cells[i]);
}
}
free(state->cells);
}
void life_cb_pause(GameData *game, bool is_paused, void *cb_data)
{
if (!cb_data) {
return;
}
LifeState *state = (LifeState *)cb_data;
state->paused = is_paused;
}
void life_cb_kill(GameData *game, void *cb_data)
{
if (!cb_data) {
return;
}
LifeState *state = (LifeState *)cb_data;
life_free_cells(state);
free(state);
game_set_cb_update_state(game, NULL, NULL);
game_set_cb_render_window(game, NULL, NULL);
game_set_cb_kill(game, NULL, NULL);
game_set_cb_on_keypress(game, NULL, NULL);
}
static int life_init_state(GameData *game, LifeState *state)
{
const int x_left = game_x_left_bound(game) ;
const int x_right = game_x_right_bound(game);
const int y_top = game_y_top_bound(game);
const int y_bottom = game_y_bottom_bound(game) + 1;
state->x_left_bound = x_left;
state->x_right_bound = x_right;
state->y_top_bound = y_top;
state->y_bottom_bound = y_bottom;
const int x_mid = x_left + ((x_right - x_left) / 2);
const int y_mid = y_top + ((y_bottom - y_top) / 2);
state->curs_x = x_mid;
state->curs_y = y_mid;
const int num_rows = (y_bottom - y_top) + LIFE_BOUNDARY_BUFFER;
const int num_columns = (x_right - x_left) + LIFE_BOUNDARY_BUFFER;
if (num_rows <= 0 || num_columns <= 0) {
return -1;
}
state->num_columns = num_columns;
state->num_rows = num_rows;
state->cells = calloc(1, num_rows * sizeof(Cell *));
if (state->cells == NULL) {
return -1;
}
for (int i = 0; i < num_rows; ++i) {
state->cells[i] = calloc(1, num_columns * sizeof(Cell));
if (state->cells[i] == NULL) {
return -1;
}
for (int j = 0; j < num_columns; ++j) {
state->cells[i][j].coords.y = i + (state->y_top_bound - (LIFE_BOUNDARY_BUFFER / 2));
state->cells[i][j].coords.x = j + (state->x_left_bound - (LIFE_BOUNDARY_BUFFER / 2));
}
}
state->speed = LIFE_DEFAULT_SPEED;
state->colour = LIFE_CELL_DEFAULT_COLOUR;
life_restart(game, state);
return 0;
}
int life_initialize(GameData *game)
{
// Try best fit from largest to smallest before giving up
if (game_set_window_shape(game, GW_ShapeRectangleLarge) == -1) {
if (game_set_window_shape(game, GW_ShapeSquareLarge) == -1) {
if (game_set_window_shape(game, GW_ShapeRectangle) == -1) {
if (game_set_window_shape(game, GW_ShapeSquare) == -1) {
return -1;
}
}
}
}
LifeState *state = calloc(1, sizeof(LifeState));
if (state == NULL) {
return -1;
}
if (life_init_state(game, state) == -1) {
life_free_cells(state);
free(state);
return -1;
}
game_set_update_interval(game, 40);
game_show_score(game, true);
game_set_cb_update_state(game, life_cb_update_game_state, state);
game_set_cb_render_window(game, life_cb_render_window, state);
game_set_cb_on_keypress(game, life_cb_on_keypress, state);
game_set_cb_on_pause(game, life_cb_pause, state);
game_set_cb_kill(game, life_cb_kill, state);
return 0;
}

31
src/game_life.h Normal file
View File

@ -0,0 +1,31 @@
/* game_life.h
*
*
* Copyright (C) 2021 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GAME_LIFE
#define GAME_LIFE
#include "game_base.h"
int life_initialize(GameData *game);
#endif // GAME_LIFE

906
src/game_snake.c Normal file
View File

@ -0,0 +1,906 @@
/* game_snake.c
*
*
* Copyright (C) 2020 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "game_base.h"
#include "game_util.h"
#include "game_snake.h"
#include "misc_tools.h"
#define SNAKE_MAX_SNAKE_LENGTH (GAME_MAX_SQUARE_X_DEFAULT * GAME_MAX_SQUARE_Y_DEFAULT)
#define SNAKE_AGENT_MAX_LIST_SIZE (GAME_MAX_SQUARE_X_DEFAULT * GAME_MAX_SQUARE_Y_DEFAULT)
#define SNAKE_DEFAULT_SNAKE_SPEED 6
#define SNAKE_DEFAULT_AGENT_SPEED 1
#define SNAKE_MAX_SNAKE_SPEED 12
#define SNAKE_MAX_AGENT_SPEED (SNAKE_MAX_SNAKE_SPEED / 2)
#define SNAKE_DEFAULT_UPDATE_INTERVAL 20
/* How often we update frames independent of the speed of the game or objects */
#define SNAKE_FRAME_DRAW_SPEED 5
/* How long a regular message stays on the screen */
#define SNAKE_DEFAULT_MESSAGE_TIMER 5
/* Increment snake speed by 1 every time level increases by this amount */
#define SNAKE_LEVEL_SPEED_INTERVAL 5
/* Increment level by 1 every time snake eats this many foods */
#define SNAKE_LEVEL_UP_FOOD_LIMIT 4
/* Increment agent speed by 1 every time level increases by this amount */
#define SNAKE_AGENT_LEVEL_SPEED_INTERVAL 2
/* Points multiplier for getting a powerup */
#define SNAKE_POWERUP_BONUS_MULTIPLIER 5
/* Extra bonus for running over a glowing agent */
#define SNAKE_AGENT_GLOWING_MULTIPLIER 2
/* Agents begin glowing if their speed is greater than this */
#define SNAKE_AGENT_GLOWING_SPEED (SNAKE_DEFAULT_AGENT_SPEED + 2)
/* A new powerup is placed on the board after this many seconds since last one wore off */
#define SNAKE_POWERUP_INTERVAL 45
/* How long a powerup lasts */
#define SNAKE_POWERUP_TIMER 12
/* Number of key presses to queue; one key press is retrieved per state update */
#define SNAKE_KEY_PRESS_QUEUE_SIZE 3
/* These decide how many points to decay and how often to penalize camping for powerups */
#define SNAKE_DECAY_POINTS_INTERVAL 1
#define SNAKE_DECAY_POINTS_FRACTION 10
#define SNAKE_HEAD_COLOUR GREEN
#define SNAKE_DEAD_BODY_CHAR 'o'
#define SNAKE_DEAD_BODY_COLOUR RED
#define SNAKE_BODY_COLOUR CYAN
#define SNAKE_BODY_CHAR 'o'
#define SNAKE_FOOD_COLOUR YELLOW
#define SNAKE_FOOD_CHAR '*'
#define SNAKE_AGENT_NORMAL_COLOUR RED
#define SNAKE_AGENT_GLOWING_COLOUR GREEN
#define SNAKE_AGENT_NORMAL_CHAR 'x'
#define SNAKE_AGENT_GLOWING_CHAR 'X'
#define SNAKE_POWERUP_CHAR 'P'
typedef struct NasaAgent {
Coords coords;
bool is_alive;
bool is_glowing;
TIME_MS last_time_moved;
size_t speed;
char display_char;
int colour;
int attributes;
} NasaAgent;
typedef struct Snake {
Coords coords;
char display_char;
int colour;
int attributes;
} Snake;
typedef struct SnakeState {
Snake *snake;
size_t snake_length;
size_t snake_speed;
TIME_MS snake_time_last_moved;
bool has_powerup;
Direction direction;
Coords powerup;
TIME_S powerup_timer;
TIME_S last_powerup_time;
Coords food;
NasaAgent *agents;
size_t agent_list_size;
TIME_S last_time_points_decayed;
TIME_S pause_time;
int key_press_queue[SNAKE_KEY_PRESS_QUEUE_SIZE];
size_t keys_skip_counter;
TIME_MS last_draw_update;
bool game_over;
} SnakeState;
static void snake_create_points_message(GameData *game, Direction dir, long int points, const Coords *coords)
{
char buf[GAME_MAX_MESSAGE_SIZE + 1];
snprintf(buf, sizeof(buf), "%ld", points);
if (game_set_message(game, buf, strlen(buf), dir, A_BOLD, WHITE, 0, coords, false, false) == -1) {
fprintf(stderr, "failed to set points message\n");
}
}
static void snake_create_message(GameData *game, Direction dir, const char *message, int attributes,
int colour, TIME_S timeout, const Coords *coords, bool priority)
{
if (game_set_message(game, message, strlen(message), dir, attributes, colour, timeout, coords, false, priority) == -1) {
fprintf(stderr, "failed to set message\n");
}
}
static Coords *snake_get_head_coords(const SnakeState *state)
{
return &state->snake[0].coords;
}
static void snake_set_head_char(SnakeState *state)
{
Snake *snake_head = &state->snake[0];
switch (state->direction) {
case NORTH:
snake_head->display_char = '^';
break;
case SOUTH:
snake_head->display_char = 'v';
break;
case EAST:
snake_head->display_char = '>';
return;
case WEST:
snake_head->display_char = '<';
break;
default:
snake_head->display_char = '?';
break;
}
}
static bool snake_validate_direction(const SnakeState *state, Direction dir)
{
if (!GAME_UTIL_DIRECTION_VALID(dir)) {
return false;
}
const int diff = abs((int)state->direction - (int)dir);
return diff != 1;
}
static void snake_update_direction(SnakeState *state)
{
for (size_t i = 0; i < SNAKE_KEY_PRESS_QUEUE_SIZE; ++i) {
int key = state->key_press_queue[i];
if (key == 0) {
continue;
}
Direction dir = game_util_get_direction(key);
if (!GAME_UTIL_DIRECTION_VALID(dir)) {
state->key_press_queue[i] = 0;
continue;
}
if (snake_validate_direction(state, dir)) {
state->direction = dir;
snake_set_head_char(state);
state->key_press_queue[i] = 0;
state->keys_skip_counter = 0;
break;
}
if (state->keys_skip_counter++ >= SNAKE_KEY_PRESS_QUEUE_SIZE) {
state->keys_skip_counter = 0;
memset(state->key_press_queue, 0, sizeof(state->key_press_queue));
}
}
}
static void snake_set_key_press(SnakeState *state, int key)
{
for (size_t i = 0; i < SNAKE_KEY_PRESS_QUEUE_SIZE; ++i) {
if (state->key_press_queue[i] == 0) {
state->key_press_queue[i] = key;
return;
}
}
memset(state->key_press_queue, 0, sizeof(state->key_press_queue));
state->key_press_queue[0] = key;
}
static void snake_update_score(GameData *game, const SnakeState *state, long int points)
{
const Coords *head = snake_get_head_coords(state);
snake_create_points_message(game, state->direction, points, head);
game_update_score(game, points);
}
static long int snake_get_move_points(const SnakeState *state)
{
return state->snake_length + (2 * state->snake_speed);
}
/* Return true if snake body is occupying given coordinates */
static bool snake_coords_contain_body(const SnakeState *state, const Coords *coords)
{
for (size_t i = 1; i < state->snake_length; ++i) {
Coords snake_coords = state->snake[i].coords;
if (COORDINATES_OVERLAP(coords->x, coords->y, snake_coords.x, snake_coords.y)) {
return true;
}
}
return false;
}
static bool snake_self_consume(const SnakeState *state)
{
const Coords *head = snake_get_head_coords(state);
return snake_coords_contain_body(state, head);
}
/*
* Returns a pointer to the agent at x and y coordinates.
*
* Returns NULL if no living agent is at coords.
*/
static NasaAgent *snake_get_agent_at_coords(const SnakeState *state, const Coords *coords)
{
for (size_t i = 0; i < state->agent_list_size; ++i) {
NasaAgent *agent = &state->agents[i];
if (!agent->is_alive) {
continue;
}
if (COORDINATES_OVERLAP(coords->x, coords->y, agent->coords.x, agent->coords.y)) {
return agent;
}
}
return NULL;
}
/*
* Return true if snake got caught by an agent and doesn't have a powerup.
*
* If snake runs over agent and does have a powerup the agent is killed and score updated.
*/
static bool snake_agent_caught(GameData *game, SnakeState *state, const Coords *coords)
{
NasaAgent *agent = snake_get_agent_at_coords(state, coords);
if (agent == NULL) {
return false;
}
if (!state->has_powerup) {
return true;
}
agent->is_alive = false;
long int points = snake_get_move_points(state) * (agent->speed + 1);
if (agent->is_glowing) {
points *= SNAKE_AGENT_GLOWING_MULTIPLIER;
}
snake_update_score(game, state, points);
return false;
}
static bool snake_state_valid(GameData *game, SnakeState *state)
{
const Coords *head = snake_get_head_coords(state);
if (!game_coordinates_in_bounds(game, head->x, head->y)) {
snake_create_message(game, state->direction, "Ouch!", A_BOLD, WHITE, SNAKE_DEFAULT_MESSAGE_TIMER, head, true);
return false;
}
if (snake_self_consume(state)) {
snake_create_message(game, state->direction, "Tastes like chicken", A_BOLD, WHITE, SNAKE_DEFAULT_MESSAGE_TIMER, head,
true);
return false;
}
if (snake_agent_caught(game, state, head)) {
snake_create_message(game, state->direction, "ARGH they got me!", A_BOLD, WHITE, SNAKE_DEFAULT_MESSAGE_TIMER, head,
true);
return false;
}
return true;
}
/*
* Sets colour and attributes for entire snake except head.
*
* If colour is set to -1 a random colour is chosen for each body part.
*/
static void snake_set_body_attributes(Snake *snake, size_t length, int colour, int attributes)
{
for (size_t i = 1; i < length; ++i) {
Snake *body = &snake[i];
if (colour == -1) {
body->colour = game_util_random_colour();
} else {
body->colour = colour;
}
body->attributes = attributes;
}
}
static void snake_move_body(SnakeState *state)
{
for (size_t i = state->snake_length - 1; i > 0; --i) {
Coords *curr = &state->snake[i].coords;
Coords prev = state->snake[i - 1].coords;
curr->x = prev.x;
curr->y = prev.y;
}
}
static void snake_move_head(SnakeState *state)
{
Coords *head = snake_get_head_coords(state);
game_util_move_coords(state->direction, head);
}
static void snake_grow(SnakeState *state)
{
size_t index = state->snake_length;
if (index >= SNAKE_MAX_SNAKE_LENGTH) {
return;
}
state->snake[index].coords.x = -1;
state->snake[index].coords.y = -1;
state->snake[index].display_char = SNAKE_BODY_CHAR;
state->snake[index].colour = SNAKE_BODY_COLOUR;
state->snake[index].attributes = A_BOLD;
state->snake_length = index + 1;
}
static long int snake_check_food(const GameData *game, SnakeState *state)
{
Coords *food = &state->food;
const Coords *head = snake_get_head_coords(state);
if (!COORDINATES_OVERLAP(head->x, head->y, food->x, food->y)) {
return 0;
}
snake_grow(state);
game_random_coords(game, food);
return snake_get_move_points(state);
}
static long int snake_check_powerup(GameData *game, SnakeState *state)
{
Coords *powerup = &state->powerup;
const Coords *head = snake_get_head_coords(state);
if (!COORDINATES_OVERLAP(head->x, head->y, powerup->x, powerup->y)) {
return 0;
}
snake_create_message(game, state->direction, "AAAAA", A_BOLD, RED, 2, head, false);
TIME_S t = get_unix_time();
state->has_powerup = true;
state->powerup_timer = t;
powerup->x = -1;
powerup->y = -1;
return snake_get_move_points(state) * SNAKE_POWERUP_BONUS_MULTIPLIER;
}
/*
* Returns the first unoccupied index in agent array.
*/
static size_t snake_get_empty_agent_index(const NasaAgent *agents)
{
for (size_t i = 0; i < SNAKE_AGENT_MAX_LIST_SIZE; ++i) {
if (!agents[i].is_alive) {
return i;
}
}
fprintf(stderr, "Warning: Agent array is full. This should be impossible\n");
return 0;
}
static void snake_initialize_agent(SnakeState *state, const Coords *coords)
{
size_t idx = snake_get_empty_agent_index(state->agents);
if ((idx >= state->agent_list_size) && (idx + 1 <= SNAKE_AGENT_MAX_LIST_SIZE)) {
state->agent_list_size = idx + 1;
}
NasaAgent *agent = &state->agents[idx];
agent->coords = (Coords) {
coords->x,
coords->y
};
agent->is_alive = true;
agent->is_glowing = false;
agent->display_char = SNAKE_AGENT_NORMAL_CHAR;
agent->colour = SNAKE_AGENT_NORMAL_COLOUR;
agent->attributes = A_BOLD;
agent->last_time_moved = 0;
agent->speed = SNAKE_DEFAULT_AGENT_SPEED;
}
static void snake_dispatch_new_agent(const GameData *game, SnakeState *state)
{
Coords new_coords;
const Coords *head = snake_get_head_coords(state);
size_t tries = 0;
do {
if (tries++ >= 10) {
return;
}
game_random_coords(game, &new_coords);
} while (COORDINATES_OVERLAP(new_coords.x, new_coords.y, head->x, head->y)
|| snake_get_agent_at_coords(state, &new_coords) != NULL);
snake_initialize_agent(state, &new_coords);
}
static void snake_place_powerup(const GameData *game, SnakeState *state)
{
Coords *powerup = &state->powerup;
if (powerup->x != -1) {
return;
}
if (!timed_out(state->last_powerup_time, SNAKE_POWERUP_INTERVAL)) {
return;
}
game_random_coords(game, powerup);
}
static void snake_do_powerup(const GameData *game, SnakeState *state)
{
if (!state->has_powerup) {
snake_place_powerup(game, state);
return;
}
if (timed_out(state->powerup_timer, SNAKE_POWERUP_TIMER)) {
state->last_powerup_time = get_unix_time();
state->has_powerup = false;
snake_set_body_attributes(state->snake, state->snake_length, SNAKE_BODY_COLOUR, A_BOLD);
}
}
static void snake_decay_points(GameData *game, SnakeState *state)
{
long int score = game_get_score(game);
long int decay = snake_get_move_points(state) / SNAKE_DECAY_POINTS_FRACTION;
if (score > decay && timed_out(state->last_time_points_decayed, SNAKE_DECAY_POINTS_INTERVAL)) {
game_update_score(game, -decay);
state->last_time_points_decayed = get_unix_time();
}
}
static void snake_do_points_update(GameData *game, SnakeState *state, long int points)
{
snake_update_score(game, state, points);
if (state->snake_length % SNAKE_LEVEL_UP_FOOD_LIMIT != 0) {
return;
}
game_increment_level(game);
size_t level = game_get_current_level(game);
if (level % SNAKE_LEVEL_SPEED_INTERVAL == 0 && state->snake_speed < SNAKE_MAX_SNAKE_SPEED) {
++state->snake_speed;
}
if (level % SNAKE_AGENT_LEVEL_SPEED_INTERVAL == 0) {
for (size_t i = 0; i < state->agent_list_size; ++i) {
NasaAgent *agent = &state->agents[i];
if (!agent->is_alive) {
continue;
}
if (agent->speed < SNAKE_MAX_AGENT_SPEED) {
++agent->speed;
}
if (agent->speed > SNAKE_AGENT_GLOWING_SPEED && !agent->is_glowing) {
agent->is_glowing = true;
agent->display_char = SNAKE_AGENT_GLOWING_CHAR;
agent->colour = SNAKE_AGENT_GLOWING_COLOUR;
snake_create_message(game, state->direction, "*glows*", A_BOLD, GREEN, 2, &agent->coords, false);
}
}
}
snake_dispatch_new_agent(game, state);
}
static void snake_game_over(SnakeState *state)
{
state->game_over = true;
state->has_powerup = false;
state->snake[0].colour = SNAKE_DEAD_BODY_COLOUR;
state->snake[0].attributes = A_BOLD | A_BLINK;
snake_set_body_attributes(state->snake, state->snake_length, SNAKE_DEAD_BODY_COLOUR, A_BOLD | A_BLINK);
}
static void snake_move(GameData *game, SnakeState *state, TIME_MS cur_time)
{
const TIME_MS real_speed = GAME_UTIL_REAL_SPEED(state->direction, state->snake_speed);
if (!game_do_object_state_update(game, cur_time, state->snake_time_last_moved, real_speed)) {
return;
}
state->snake_time_last_moved = cur_time;
snake_update_direction(state);
snake_move_body(state);
snake_move_head(state);
if (!snake_state_valid(game, state)) {
snake_game_over(state);
game_set_status(game, GS_Finished);
return;
}
long int points = snake_check_food(game, state) + snake_check_powerup(game, state);
if (points > 0) {
snake_do_points_update(game, state, points);
}
}
/*
* Attempts to move every agent in list.
*
* If an agent is normal it will move in a random direction. If it's glowing it will
* move towards the snake.
*/
static void snake_agent_move(GameData *game, SnakeState *state, TIME_MS cur_time)
{
const Coords *head = snake_get_head_coords(state);
for (size_t i = 0; i < state->agent_list_size; ++i) {
NasaAgent *agent = &state->agents[i];
if (!agent->is_alive) {
continue;
}
Coords *coords = &agent->coords;
Coords new_coords = (Coords) {
coords->x,
coords->y
};
Direction dir = !agent->is_glowing ? game_util_random_direction()
: game_util_move_towards(coords, head, state->has_powerup);
const TIME_MS real_speed = agent->is_glowing ? GAME_UTIL_REAL_SPEED(dir, agent->speed) : agent->speed;
if (!game_do_object_state_update(game, cur_time, agent->last_time_moved, real_speed)) {
continue;
}
agent->last_time_moved = cur_time;
game_util_move_coords(dir, &new_coords);
if (!game_coordinates_in_bounds(game, new_coords.x, new_coords.y)) {
continue;
}
if (snake_coords_contain_body(state, &new_coords)) {
continue;
}
if (snake_get_agent_at_coords(state, &new_coords) != NULL) {
continue;
}
coords->x = new_coords.x;
coords->y = new_coords.y;
if (!state->has_powerup && COORDINATES_OVERLAP(head->x, head->y, new_coords.x, new_coords.y)) {
snake_game_over(state);
game_set_status(game, GS_Finished);
return;
}
}
}
static void snake_update_frames(const GameData *game, SnakeState *state, TIME_MS cur_time)
{
if (!game_do_object_state_update(game, cur_time, state->last_draw_update, SNAKE_FRAME_DRAW_SPEED)) {
return;
}
state->last_draw_update = cur_time;
if (state->has_powerup) {
const int time_left = SNAKE_POWERUP_TIMER - (get_unix_time() - state->powerup_timer);
if (time_left <= 5 && time_left % 2 == 0) {
snake_set_body_attributes(state->snake, state->snake_length, SNAKE_BODY_COLOUR, A_BOLD);
} else {
snake_set_body_attributes(state->snake, state->snake_length, -1, A_BOLD);
}
}
}
static void snake_draw_self(WINDOW *win, const SnakeState *state)
{
for (size_t i = 0; i < state->snake_length; ++i) {
const Snake *body = &state->snake[i];
if (body->coords.x <= 0 || body->coords.y <= 0) {
continue;
}
wattron(win, body->attributes | COLOR_PAIR(body->colour));
mvwaddch(win, body->coords.y, body->coords.x, body->display_char);
wattroff(win, body->attributes | COLOR_PAIR(body->colour));
}
}
static void snake_draw_food(WINDOW *win, const SnakeState *state)
{
wattron(win, A_BOLD | COLOR_PAIR(SNAKE_FOOD_COLOUR));
mvwaddch(win, state->food.y, state->food.x, SNAKE_FOOD_CHAR);
wattroff(win, A_BOLD | COLOR_PAIR(SNAKE_FOOD_COLOUR));
}
static void snake_draw_agent(WINDOW *win, const SnakeState *state)
{
for (size_t i = 0; i < state->agent_list_size; ++i) {
NasaAgent agent = state->agents[i];
if (agent.is_alive) {
Coords coords = agent.coords;
wattron(win, agent.attributes | COLOR_PAIR(agent.colour));
mvwaddch(win, coords.y, coords.x, agent.display_char);
wattroff(win, agent.attributes | COLOR_PAIR(agent.colour));
}
}
}
static void snake_draw_powerup(WINDOW *win, const SnakeState *state)
{
Coords powerup = state->powerup;
if (powerup.x != -1) {
int colour = game_util_random_colour();
wattron(win, A_BOLD | COLOR_PAIR(colour));
mvwaddch(win, powerup.y, powerup.x, SNAKE_POWERUP_CHAR);
wattroff(win, A_BOLD | COLOR_PAIR(colour));
}
}
void snake_cb_update_game_state(GameData *game, void *cb_data)
{
if (!cb_data) {
return;
}
SnakeState *state = (SnakeState *)cb_data;
TIME_MS cur_time = get_time_millis();
snake_do_powerup(game, state);
snake_agent_move(game, state, cur_time);
snake_move(game, state, cur_time);
snake_decay_points(game, state);
if (!state->game_over) {
snake_update_frames(game, state, cur_time);
}
}
void snake_cb_render_window(GameData *game, WINDOW *win, void *cb_data)
{
if (!cb_data) {
return;
}
SnakeState *state = (SnakeState *)cb_data;
snake_draw_food(win, state);
snake_draw_powerup(win, state);
snake_draw_agent(win, state);
snake_draw_self(win, state);
}
void snake_cb_kill(GameData *game, void *cb_data)
{
if (!cb_data) {
return;
}
SnakeState *state = (SnakeState *)cb_data;
free(state->snake);
free(state->agents);
free(state);
game_set_cb_update_state(game, NULL, NULL);
game_set_cb_render_window(game, NULL, NULL);
game_set_cb_kill(game, NULL, NULL);
game_set_cb_on_keypress(game, NULL, NULL);
game_set_cb_on_pause(game, NULL, NULL);
}
void snake_cb_on_keypress(GameData *game, int key, void *cb_data)
{
if (!cb_data) {
return;
}
SnakeState *state = (SnakeState *)cb_data;
snake_set_key_press(state, key);
}
void snake_cb_pause(GameData *game, bool is_paused, void *cb_data)
{
UNUSED_VAR(game);
if (!cb_data) {
return;
}
SnakeState *state = (SnakeState *)cb_data;
TIME_S t = get_unix_time();
if (is_paused) {
state->pause_time = t;
} else {
state->powerup_timer += (t - state->pause_time);
state->last_powerup_time += (t - state->pause_time);
}
}
static void snake_initialize_snake_head(const GameData *game, Snake *snake)
{
int max_x;
int max_y;
game_max_x_y(game, &max_x, &max_y);
snake[0].coords.x = max_x / 2;
snake[0].coords.y = max_y / 2;
snake[0].colour = SNAKE_HEAD_COLOUR;
snake[0].attributes = A_BOLD;
}
int snake_initialize(GameData *game)
{
// note: if this changes we must update SNAKE_MAX_SNAKE_LENGTH and SNAKE_AGENT_MAX_LIST_SIZE
if (game_set_window_shape(game, GW_ShapeSquare) == -1) {
return -1;
}
SnakeState *state = calloc(1, sizeof(SnakeState));
if (state == NULL) {
return -1;
}
state->snake = calloc(1, SNAKE_MAX_SNAKE_LENGTH * sizeof(Snake));
if (state->snake == NULL) {
free(state);
return -1;
}
state->agents = calloc(1, SNAKE_AGENT_MAX_LIST_SIZE * sizeof(NasaAgent));
if (state->agents == NULL) {
free(state->snake);
free(state);
return -1;
}
snake_initialize_snake_head(game, state->snake);
state->snake_speed = SNAKE_DEFAULT_SNAKE_SPEED;
state->snake_length = 1;
state->direction = NORTH;
snake_set_head_char(state);
state->powerup.x = -1;
state->powerup.y = -1;
state->last_powerup_time = get_unix_time();
game_show_level(game, true);
game_show_score(game, true);
game_show_high_score(game, true);
game_increment_level(game);
game_set_update_interval(game, SNAKE_DEFAULT_UPDATE_INTERVAL);
game_random_coords(game, &state->food);
game_set_cb_update_state(game, snake_cb_update_game_state, state);
game_set_cb_render_window(game, snake_cb_render_window, state);
game_set_cb_on_keypress(game, snake_cb_on_keypress, state);
game_set_cb_kill(game, snake_cb_kill, state);
game_set_cb_on_pause(game, snake_cb_pause, state);
return 0;
}

30
src/game_snake.h Normal file
View File

@ -0,0 +1,30 @@
/* game_snake.h
*
*
* Copyright (C) 2020 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GAME_SNAKE
#define GAME_SNAKE
#include "game_base.h"
int snake_initialize(GameData *game);
#endif // GAME_SNAKE

200
src/game_util.c Normal file
View File

@ -0,0 +1,200 @@
/* game_util.c
*
*
* Copyright (C) 2020 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "game_util.h"
#include "toxic.h"
#include "windows.h"
Direction game_util_get_direction(int key)
{
switch (key) {
case KEY_UP: {
return NORTH;
}
case KEY_DOWN: {
return SOUTH;
}
case KEY_RIGHT: {
return EAST;
}
case KEY_LEFT: {
return WEST;
}
default: {
return INVALID_DIRECTION;
}
}
}
Direction game_util_move_towards(const Coords *coords_a, const Coords *coords_b, bool inverse)
{
const int x1 = coords_a->x;
const int y1 = coords_a->y;
const int x2 = coords_b->x;
const int y2 = coords_b->y;
const int x_diff = abs(x1 - x2);
const int y_diff = abs(y1 - y2);
if (inverse) {
if (x_diff > y_diff) {
return x2 >= x1 ? WEST : EAST;
} else {
return y2 >= y1 ? NORTH : SOUTH;
}
} else {
if (x_diff > y_diff) {
return x2 < x1 ? WEST : EAST;
} else {
return y2 < y1 ? NORTH : SOUTH;
}
}
}
Direction game_util_random_direction(void)
{
int r = rand() % 4;
switch (r) {
case 0:
return NORTH;
case 1:
return SOUTH;
case 2:
return EAST;
case 3:
return WEST;
default: // impossible
return NORTH;
}
}
void game_util_move_coords(Direction direction, Coords *coords)
{
switch (direction) {
case NORTH: {
if (coords->y > 0) {
--(coords->y);
}
break;
}
case SOUTH: {
++(coords->y); // Will rollover if you do something stupid
break;
}
case EAST: {
++(coords->x); // Will rollover if you do something stupid
break;
}
case WEST: {
if (coords->x > 0) {
--(coords->x);
}
break;
}
default: {
fprintf(stderr, "Warning: tried to move in an invalid direction\n");
return;
}
}
}
int game_util_random_colour(void)
{
int r = rand() % 6;
switch (r) {
case 0:
return GREEN;
case 1:
return CYAN;
case 2:
return RED;
case 3:
return BLUE;
case 4:
return YELLOW;
case 5:
return MAGENTA;
default: // impossible
return RED;
}
}
static size_t net_pack_u16(uint8_t *bytes, uint16_t v)
{
bytes[0] = (v >> 8) & 0xff;
bytes[1] = v & 0xff;
return sizeof(v);
}
static size_t net_unpack_u16(const uint8_t *bytes, uint16_t *v)
{
uint8_t hi = bytes[0];
uint8_t lo = bytes[1];
*v = ((uint16_t)hi << 8) | lo;
return sizeof(*v);
}
size_t game_util_pack_u32(uint8_t *bytes, uint32_t v)
{
uint8_t *p = bytes;
p += net_pack_u16(p, (v >> 16) & 0xffff);
p += net_pack_u16(p, v & 0xffff);
return p - bytes;
}
size_t game_util_unpack_u32(const uint8_t *bytes, uint32_t *v)
{
const uint8_t *p = bytes;
uint16_t hi;
uint16_t lo;
p += net_unpack_u16(p, &hi);
p += net_unpack_u16(p, &lo);
*v = ((uint32_t)hi << 16) | lo;
return p - bytes;
}

103
src/game_util.h Normal file
View File

@ -0,0 +1,103 @@
/* game_util.h
*
*
* Copyright (C) 2020 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GAME_UTIL
#define GAME_UTIL
#include <stdbool.h>
#include <time.h>
typedef struct Coords {
int x;
int y;
} Coords;
// don't change these
typedef enum Direction {
NORTH = 0u,
SOUTH = 1,
EAST = 3,
WEST = 4,
INVALID_DIRECTION = 5
} Direction;
/*
* Use these for ms and second timestamps respectively so we don't accidentally interchange them
*/
typedef int64_t TIME_MS;
typedef time_t TIME_S;
/*
* Return true if coordinates x1, y1 overlap with x2, y2.
*/
#define COORDINATES_OVERLAP(x1, y1, x2, y2)(((x1) == (x2)) && ((y1) == (y2)))
/*
* Halves speed if moving north or south. This accounts for the fact that Y steps are twice as large as X steps.
*/
#define GAME_UTIL_REAL_SPEED(dir, speed)((((dir) == (NORTH)) || ((dir) == (SOUTH))) ? (MAX(1, ((speed) / 2))) : (speed))
/*
* Return true if dir is a valid Direction.
*/
#define GAME_UTIL_DIRECTION_VALID(dir)((dir) < (INVALID_DIRECTION))
/*
* Returns cardinal direction mapped to `key`.
*/
Direction game_util_get_direction(int key);
/*
* Returns the direction that will move `coords_a` closest to `coords_b`.
*
* If `inverse` is true, returns the opposite result.
*/
Direction game_util_move_towards(const Coords *coords_a, const Coords *coords_b, bool inverse);
/*
* Returns a random direction.
*/
Direction game_util_random_direction(void);
/*
* Moves `coords` one square towards `direction`.
*/
void game_util_move_coords(Direction direction, Coords *coords);
/*
* Returns a random colour.
*/
int game_util_random_colour(void);
/*
* Packs an unsigned 32 bit integer `v` into `bytes`.
*/
size_t game_util_pack_u32(uint8_t *bytes, uint32_t v);
/*
* Unpacks an unsigned 32 bit integer in `bytes` to `v`.
*/
size_t game_util_unpack_u32(const uint8_t *bytes, uint32_t *v);
#endif // GAME_UTIL

View File

@ -23,20 +23,24 @@
#include <stdlib.h>
#include <string.h>
#include "toxic.h"
#include "windows.h"
#include "misc_tools.h"
#include "friendlist.h"
#include "log.h"
#include "line_info.h"
#include "groupchat.h"
#include "prompt.h"
#include "help.h"
#include "term_mplex.h"
#include "avatars.h"
#include "conference.h"
#include "friendlist.h"
#include "help.h"
#include "line_info.h"
#include "log.h"
#include "misc_tools.h"
#include "name_lookup.h"
#include "prompt.h"
#include "qr_code.h"
#include "term_mplex.h"
#include "toxic.h"
#include "toxic_strings.h"
#include "windows.h"
#ifdef GAMES
#include "game_base.h"
#endif
extern char *DATA_FILE;
extern ToxWindow *prompt;
@ -46,54 +50,58 @@ extern FriendRequests FrndRequests;
/* command functions */
void cmd_accept(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Request ID required.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Request ID required.");
return;
}
long int req = strtol(argv[1], NULL, 10);
if ((req == 0 && strcmp(argv[1], "0")) || req < 0 || req >= MAX_FRIEND_REQUESTS) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending friend request with that ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending friend request with that ID.");
return;
}
if (!FrndRequests.request[req].active) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending friend request with that ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending friend request with that ID.");
return;
}
TOX_ERR_FRIEND_ADD err;
Tox_Err_Friend_Add err;
uint32_t friendnum = tox_friend_add_norequest(m, FrndRequests.request[req].key, &err);
if (err != TOX_ERR_FRIEND_ADD_OK) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to add friend (error %d\n)", err);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to add friend (error %d\n)", err);
return;
} else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Friend request accepted.");
on_friendadded(m, friendnum, true);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Friend request accepted.");
on_friend_added(m, friendnum, true);
}
memset(&FrndRequests.request[req], 0, sizeof(struct friend_request));
FrndRequests.request[req] = (struct friend_request) {
0
};
int i;
for (i = FrndRequests.max_idx; i > 0; --i) {
if (FrndRequests.request[i - 1].active)
if (FrndRequests.request[i - 1].active) {
break;
}
}
FrndRequests.max_idx = i;
--FrndRequests.num_requests;
}
void cmd_add_helper(ToxWindow *self, Tox *m, const char *id_bin, const char *msg)
{
const char *errmsg;
TOX_ERR_FRIEND_ADD err;
uint32_t f_num = tox_friend_add(m, (uint8_t *) id_bin, (uint8_t *) msg, strlen(msg), &err);
Tox_Err_Friend_Add err;
uint32_t f_num = tox_friend_add(m, (const uint8_t *) id_bin, (const uint8_t *) msg, strlen(msg), &err);
switch (err) {
case TOX_ERR_FRIEND_ADD_TOO_LONG:
@ -126,24 +134,26 @@ void cmd_add_helper(ToxWindow *self, Tox *m, const char *id_bin, const char *msg
case TOX_ERR_FRIEND_ADD_OK:
errmsg = "Friend request sent.";
on_friendadded(m, f_num, true);
on_friend_added(m, f_num, true);
break;
case TOX_ERR_FRIEND_ADD_NULL:
/* fallthrough */
default:
errmsg = "Faile to add friend: Unknown error.";
errmsg = "Failed to add friend: Unknown error.";
break;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, errmsg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, errmsg);
}
void cmd_add(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Tox ID or address required.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Tox ID or address required.");
return;
}
@ -152,7 +162,7 @@ void cmd_add(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
if (argc > 1) {
if (argv[2][0] != '\"') {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Message must be enclosed in quotes.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Message must be enclosed in quotes.");
return;
}
@ -186,13 +196,18 @@ void cmd_add(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
xx[2] = '\0';
if (sscanf(xx, "%02x", &x) != 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid Tox ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid Tox ID.");
return;
}
id_bin[i] = x;
}
if (friend_is_blocked(id_bin)) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Friend is in your block list.");
return;
}
cmd_add_helper(self, m, id_bin, msg);
} else { /* assume id is a username@domain address and do http name server lookup */
name_lookup(self, m, id_bin, id, msg);
@ -201,24 +216,20 @@ void cmd_add(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
void cmd_avatar(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
if (argc < 2 || strlen(argv[1]) < 3) {
UNUSED_VAR(window);
if (argc != 1 || strlen(argv[1]) < 3) {
avatar_unset(m);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Avatar is not set.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Avatar has been unset.");
return;
}
if (argv[1][0] != '\"') {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Path must be enclosed in quotes.");
return;
}
/* remove opening and closing quotes */
char path[MAX_STR_SIZE];
snprintf(path, sizeof(path), "%s", &argv[1][1]);
int len = strlen(path) - 1;
snprintf(path, sizeof(path), "%s", argv[1]);
int len = strlen(path);
if (len <= 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid path.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid path.");
return;
}
@ -227,25 +238,31 @@ void cmd_avatar(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
get_file_name(filename, sizeof(filename), path);
if (avatar_set(m, path, len) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0,
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"Failed to set avatar. Avatars must be in PNG format and may not exceed %d bytes.",
MAX_AVATAR_FILE_SIZE);
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Avatar set to '%s'", filename);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Avatar set to '%s'", filename);
}
void cmd_clear(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(m);
UNUSED_VAR(argc);
UNUSED_VAR(argv);
line_info_clear(self->chatwin->hst);
force_refresh(window);
}
void cmd_connect(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc != 3) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Require: <ip> <port> <key>");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Require: <ip> <port> <key>");
return;
}
@ -256,32 +273,32 @@ void cmd_connect(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)
long int port = strtol(port_str, NULL, 10);
if (port <= 0 || port > MAX_PORT_RANGE) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid port.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid port.");
return;
}
char key_binary[TOX_PUBLIC_KEY_SIZE * 2 + 1];
if (hex_string_to_bin(ascii_key, strlen(ascii_key), key_binary, TOX_PUBLIC_KEY_SIZE) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid key.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid key.");
return;
}
TOX_ERR_BOOTSTRAP err;
Tox_Err_Bootstrap err;
tox_bootstrap(m, ip, port, (uint8_t *) key_binary, &err);
tox_add_tcp_relay(m, ip, port, (uint8_t *) key_binary, &err);
switch (err) {
case TOX_ERR_BOOTSTRAP_BAD_HOST:
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Bootstrap failed: Invalid IP.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Bootstrap failed: Invalid IP.");
break;
case TOX_ERR_BOOTSTRAP_BAD_PORT:
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Bootstrap failed: Invalid port.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Bootstrap failed: Invalid port.");
break;
case TOX_ERR_BOOTSTRAP_NULL:
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Bootstrap failed.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Bootstrap failed.");
break;
default:
@ -291,155 +308,238 @@ void cmd_connect(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)
void cmd_decline(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(m);
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Request ID required.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Request ID required.");
return;
}
long int req = strtol(argv[1], NULL, 10);
if ((req == 0 && strcmp(argv[1], "0")) || req < 0 || req >= MAX_FRIEND_REQUESTS) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending friend request with that ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending friend request with that ID.");
return;
}
if (!FrndRequests.request[req].active) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending friend request with that ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending friend request with that ID.");
return;
}
memset(&FrndRequests.request[req], 0, sizeof(struct friend_request));
FrndRequests.request[req] = (struct friend_request) {
0
};
int i;
for (i = FrndRequests.max_idx; i > 0; --i) {
if (FrndRequests.request[i - 1].active)
if (FrndRequests.request[i - 1].active) {
break;
}
}
FrndRequests.max_idx = i;
--FrndRequests.num_requests;
}
void cmd_groupchat(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
#ifdef GAMES
void cmd_game(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc < 1) {
game_list_print(self);
return;
}
GameType type = game_get_type(argv[1]);
if (type >= GT_Invalid) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Unknown game.");
return;
}
if (get_num_active_windows() >= MAX_WINDOWS_NUM) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, " * Warning: Too many windows are open.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Warning: Too many windows are open.");
return;
}
uint32_t id = rand();
int ret = game_initialize(self, m, type, id, NULL, 0);
switch (ret) {
case 0: {
break;
}
case -1: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Window is too small.");
return;
}
case -2: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize: Network error.");
return;
}
case -3: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"Game is multiplayer only. Try the command again in the chat window of the contact you wish to play with.");
return;
}
default: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (error %d)", ret);
return;
}
}
}
#endif // GAMES
void cmd_conference(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (get_num_active_windows() >= MAX_WINDOWS_NUM) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Warning: Too many windows are open.");
return;
}
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Please specify group type: text | audio");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Please specify conference type: text | audio");
return;
}
uint8_t type;
if (!strcasecmp(argv[1], "audio"))
type = TOX_GROUPCHAT_TYPE_AV;
else if (!strcasecmp(argv[1], "text"))
type = TOX_GROUPCHAT_TYPE_TEXT;
else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Valid group types are: text | audio");
if (!strcasecmp(argv[1], "audio")) {
type = TOX_CONFERENCE_TYPE_AV;
} else if (!strcasecmp(argv[1], "text")) {
type = TOX_CONFERENCE_TYPE_TEXT;
} else {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Valid conference types are: text | audio");
return;
}
int groupnum = -1;
uint32_t conferencenum = 0;
if (type == TOX_GROUPCHAT_TYPE_TEXT)
groupnum = tox_add_groupchat(m);
if (type == TOX_CONFERENCE_TYPE_TEXT) {
Tox_Err_Conference_New err;
/*#ifdef AUDIO
else
groupnum = toxav_add_av_groupchat(m, NULL, NULL);
#endif*/
conferencenum = tox_conference_new(m, &err);
if (groupnum == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Group chat instance failed to initialize.");
if (err != TOX_ERR_CONFERENCE_NEW_OK) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Conference instance failed to initialize (error %d)", err);
return;
}
} else if (type == TOX_CONFERENCE_TYPE_AV) {
#ifdef AUDIO
conferencenum = toxav_add_av_groupchat(m, audio_conference_callback, NULL);
if (conferencenum == (uint32_t) -1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Audio conference instance failed to initialize");
return;
}
#else
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Audio support disabled by compile-time option.");
return;
#endif
}
if (init_conference_win(m, conferencenum, type, NULL, 0) == -1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Conference window failed to initialize.");
tox_conference_delete(m, conferencenum, NULL);
return;
}
if (init_groupchat_win(prompt, m, groupnum, type) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Group chat window failed to initialize.");
tox_del_groupchat(m, groupnum);
return;
#ifdef AUDIO
if (type == TOX_CONFERENCE_TYPE_AV) {
if (!init_conference_audio_input(m, conferencenum)) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Audio capture failed; use \"/audio on\" to try again.");
}
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Group chat [%d] created.", groupnum);
#endif
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Conference [%d] created.", conferencenum);
}
void cmd_log(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
const char *msg;
struct chatlog *log = self->chatwin->log;
if (argc == 0) {
if (log->log_on)
if (log->log_on) {
msg = "Logging for this window is ON; type \"/log off\" to disable. (Logs are not encrypted)";
else
} else {
msg = "Logging for this window is OFF; type \"/log on\" to enable.";
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, msg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, msg);
return;
}
const char *swch = argv[1];
if (!strcmp(swch, "1") || !strcmp(swch, "on")) {
char myid[TOX_ADDRESS_SIZE];
tox_self_get_address(m, (uint8_t *) myid);
int log_ret = -1;
if (self->is_chat) {
Friends.list[self->num].logging_on = true;
log_ret = log_enable(self->name, myid, Friends.list[self->num].pub_key, log, LOG_CHAT);
} else if (self->is_prompt) {
log_ret = log_enable(self->name, myid, NULL, log, LOG_PROMPT);
} else if (self->is_groupchat) {
log_ret = log_enable(self->name, myid, NULL, log, LOG_GROUP);
}
msg = log_ret == 0 ? "Logging enabled." : "Warning: Log failed to initialize.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, msg);
msg = log_enable(log) == 0 ? "Logging enabled." : "Warning: Failed to enable log.";
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, msg);
return;
} else if (!strcmp(swch, "0") || !strcmp(swch, "off")) {
if (self->is_chat)
if (self->type == WINDOW_TYPE_CHAT) {
Friends.list[self->num].logging_on = false;
}
log_disable(log);
msg = "Logging disabled.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, msg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, msg);
return;
}
msg = "Invalid option. Use \"/log on\" and \"/log off\" to toggle logging.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, msg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, msg);
}
void cmd_myid(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(argc);
UNUSED_VAR(argv);
char id_string[TOX_ADDRESS_SIZE * 2 + 1];
char bin_id[TOX_ADDRESS_SIZE];
tox_self_get_address(m, (uint8_t *) bin_id);
if (bin_id_to_string(bin_id, sizeof(bin_id), id_string, sizeof(id_string)) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to print ID.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to print ID.");
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", id_string);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", id_string);
}
#ifdef QRCODE
void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
char id_string[TOX_ADDRESS_SIZE * 2 + 1];
char bin_id[TOX_ADDRESS_SIZE];
tox_self_get_address(m, (uint8_t *) bin_id);
if (bin_id_to_string(bin_id, sizeof(bin_id), id_string, sizeof(id_string)) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code.");
return;
}
@ -449,68 +549,97 @@ void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
nick[nick_len] = '\0';
size_t data_file_len = strlen(DATA_FILE);
char dir[data_file_len + 1];
char *dir = malloc(data_file_len + 1);
if (dir == NULL) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code: Out of memory.");
return;
}
size_t dir_len = get_base_dir(DATA_FILE, data_file_len, dir);
#ifdef QRPNG
if (argc == 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Required 'txt' or 'png'");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Required 'txt' or 'png'");
free(dir);
return;
} else if (!strcmp(argv[1], "txt")) {
#endif /* QRPNG */
char qr_path[dir_len + nick_len + strlen(QRCODE_FILENAME_EXT) + 1];
snprintf(qr_path, sizeof(qr_path), "%s%s%s", dir, nick, QRCODE_FILENAME_EXT);
size_t qr_path_buf_size = dir_len + nick_len + sizeof(QRCODE_FILENAME_EXT);
char *qr_path = malloc(qr_path_buf_size);
if (ID_to_QRcode_txt(id_string, qr_path) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code.");
if (qr_path == NULL) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code: Out of memory");
free(dir);
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "QR code has been printed to the file '%s'", qr_path);
snprintf(qr_path, qr_path_buf_size, "%s%s%s", dir, nick, QRCODE_FILENAME_EXT);
if (ID_to_QRcode_txt(id_string, qr_path) == -1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code.");
free(dir);
free(qr_path);
return;
}
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "QR code has been printed to the file '%s'", qr_path);
free(qr_path);
#ifdef QRPNG
} else if (!strcmp(argv[1], "png")) {
char qr_path[dir_len + nick_len + strlen(QRCODE_FILENAME_EXT_PNG) + 1];
snprintf(qr_path, sizeof(qr_path), "%s%s%s", dir, nick, QRCODE_FILENAME_EXT_PNG);
size_t qr_path_buf_size = dir_len + nick_len + sizeof(QRCODE_FILENAME_EXT_PNG);
char *qr_path = malloc(qr_path_buf_size);
if (ID_to_QRcode_png(id_string, qr_path) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code.");
if (qr_path == NULL) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code: Out of memory");
free(dir);
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "QR code has been printed to the file '%s'", qr_path);
snprintf(qr_path, qr_path_buf_size, "%s%s%s", dir, nick, QRCODE_FILENAME_EXT_PNG);
if (ID_to_QRcode_png(id_string, qr_path) == -1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code.");
free(dir);
free(qr_path);
return;
}
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "QR code has been printed to the file '%s'", qr_path);
free(qr_path);
} else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Unknown option '%s' -- Required 'txt' or 'png'", argv[1]);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Unknown option '%s' -- Required 'txt' or 'png'", argv[1]);
free(dir);
return;
}
#endif /* QRPNG */
free(dir);
}
#endif /* QRCODE */
void cmd_nick(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Input required.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Input required.");
return;
}
char nick[MAX_STR_SIZE];
size_t len = 0;
if (argv[1][0] == '\"') { /* remove opening and closing quotes */
snprintf(nick, sizeof(nick), "%s", &argv[1][1]);
len = strlen(nick) - 1;
nick[len] = '\0';
} else {
snprintf(nick, sizeof(nick), "%s", argv[1]);
len = strlen(nick);
}
snprintf(nick, sizeof(nick), "%s", argv[1]);
size_t len = strlen(nick);
if (!valid_nick(nick)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid name.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid name.");
return;
}
@ -525,24 +654,14 @@ void cmd_nick(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
void cmd_note(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Input required.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Input required.");
return;
}
if (argv[1][0] != '\"') {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Note must be enclosed in quotes.");
return;
}
/* remove opening and closing quotes and replace linebreaks with spaces */
char msg[MAX_STR_SIZE];
snprintf(msg, sizeof(msg), "%s", &argv[1][1]);
int len = strlen(msg) - 1;
msg[len] = '\0';
strsubst(msg, '\n', ' ');
prompt_update_statusmessage(prompt, m, msg);
prompt_update_statusmessage(prompt, m, argv[1]);
}
void cmd_nospam(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
@ -553,7 +672,7 @@ void cmd_nospam(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
nospam = strtol(argv[1], NULL, 16);
if ((nospam == 0 && strcmp(argv[1], "0")) || nospam < 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid nospam value.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid nospam value.");
return;
}
}
@ -561,29 +680,44 @@ void cmd_nospam(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
uint32_t old_nospam = tox_self_get_nospam(m);
tox_self_set_nospam(m, (uint32_t) nospam);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Your new Tox ID is:");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Your new Tox ID is:");
cmd_myid(window, self, m, 0, NULL);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "");
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0,
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"Any services that relied on your old ID will need to be updated manually.");
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "If you ever want your old Tox ID back, type '/nospam %X'",
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "If you ever want your old Tox ID back, type '/nospam %X'",
old_nospam);
}
void cmd_prompt_help(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(m);
UNUSED_VAR(argc);
UNUSED_VAR(argv);
help_init_menu(self);
}
void cmd_quit(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(argc);
UNUSED_VAR(argv);
UNUSED_VAR(self);
exit_toxic_success(m);
}
void cmd_requests(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(m);
UNUSED_VAR(argc);
UNUSED_VAR(argv);
if (FrndRequests.num_requests == 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending friend requests.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending friend requests.");
return;
}
@ -591,8 +725,9 @@ void cmd_requests(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv
int count = 0;
for (i = 0; i < FrndRequests.max_idx; ++i) {
if (!FrndRequests.request[i].active)
if (!FrndRequests.request[i].active) {
continue;
}
char id[TOX_PUBLIC_KEY_SIZE * 2 + 1] = {0};
@ -602,62 +737,49 @@ void cmd_requests(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv
strcat(id, d);
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%d : %s", i, id);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", FrndRequests.request[i].msg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%d : %s", i, id);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", FrndRequests.request[i].msg);
if (++count < FrndRequests.num_requests)
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "");
if (++count < FrndRequests.num_requests) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "");
}
}
}
void cmd_status(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
bool have_note = false;
UNUSED_VAR(window);
const char *errmsg;
lock_status ();
lock_status();
if (argc >= 2) {
have_note = true;
} else if (argc < 1) {
if (argc < 1) {
errmsg = "Require a status. Statuses are: online, busy and away.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, errmsg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, errmsg);
goto finish;
}
const char *status_str = argv[1];
TOX_USER_STATUS status;
Tox_User_Status status;
if (!strcasecmp(status_str, "online"))
if (!strcasecmp(status_str, "online")) {
status = TOX_USER_STATUS_NONE;
else if (!strcasecmp(status_str, "away"))
} else if (!strcasecmp(status_str, "away")) {
status = TOX_USER_STATUS_AWAY;
else if (!strcasecmp(status_str, "busy"))
} else if (!strcasecmp(status_str, "busy")) {
status = TOX_USER_STATUS_BUSY;
else {
} else {
errmsg = "Invalid status. Valid statuses are: online, busy and away.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, errmsg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, errmsg);
goto finish;
}
tox_self_set_status(m, status);
prompt_update_status(prompt, status);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Your status has been changed to %s.", status_str);
if (have_note) {
if (argv[2][0] != '\"') {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Note must be enclosed in quotes.");
goto finish;
}
/* remove opening and closing quotes */
char msg[MAX_STR_SIZE];
snprintf(msg, sizeof(msg), "%s", &argv[2][1]);
int len = strlen(msg) - 1;
msg[len] = '\0';
prompt_update_statusmessage(prompt, m, msg);
}
finish:
unlock_status ();
unlock_status();
}

View File

@ -23,8 +23,8 @@
#ifndef GLOBAL_COMMANDS_H
#define GLOBAL_COMMANDS_H
#include "windows.h"
#include "toxic.h"
#include "windows.h"
void cmd_accept(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_add(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
@ -32,10 +32,12 @@ void cmd_avatar(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
void cmd_clear(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_connect(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_decline(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_groupchat(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_log(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_myid(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
#ifdef QRCODE
void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
#endif /* QRCODE */
void cmd_nick(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_note(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_nospam(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
@ -56,4 +58,12 @@ void cmd_list_video_devices(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)
void cmd_change_video_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
#endif /* VIDEO */
#endif /* #define GLOBAL_COMMANDS_H */
#ifdef PYTHON
void cmd_run(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
#endif /* PYTHON */
#ifdef GAMES
void cmd_game(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
#endif /* GAMES */
#endif /* GLOBAL_COMMANDS_H */

View File

@ -1,79 +0,0 @@
/* group_commands.c
*
*
* Copyright (C) 2014 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <string.h>
#include "toxic.h"
#include "windows.h"
#include "line_info.h"
#include "misc_tools.h"
#include "log.h"
void cmd_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
char title[MAX_STR_SIZE];
if (argc < 1) {
int tlen = tox_group_get_title(m, self->num, (uint8_t *) title, TOX_MAX_NAME_LENGTH);
if (tlen != -1) {
title[tlen] = '\0';
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Title is set to: %s", title);
} else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Title is not set");
}
return;
}
if (argv[1][0] != '\"') {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Title must be enclosed in quotes.");
return;
}
/* remove opening and closing quotes */
snprintf(title, sizeof(title), "%s", &argv[1][1]);
int len = strlen(title) - 1;
title[len] = '\0';
if (tox_group_set_title(m, self->num, (uint8_t *) title, len) != 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set title.");
return;
}
set_window_title(self, title, len);
char timefrmt[TIME_STR_SIZE];
char selfnick[TOX_MAX_NAME_LENGTH];
get_time_str(timefrmt, sizeof(timefrmt));
tox_self_get_name(m, (uint8_t *) selfnick);
size_t sn_len = tox_self_get_name_size(m);
selfnick[sn_len] = '\0';
line_info_add(self, timefrmt, selfnick, NULL, NAME_CHANGE, 0, 0, " set the group title to: %s", title);
char tmp_event[MAX_STR_SIZE];
snprintf(tmp_event, sizeof(tmp_event), "set title to %s", title);
write_to_log(tmp_event, selfnick, self->chatwin->log, true);
}

View File

@ -1,923 +0,0 @@
/* groupchat.c
*
*
* Copyright (C) 2014 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* needed for strcasestr() and wcswidth() */
#endif
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <wchar.h>
#include <unistd.h>
#ifdef AUDIO
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
/* compatibility with older versions of OpenAL */
#ifndef ALC_ALL_DEVICES_SPECIFIER
#include <AL/alext.h>
#endif /* ALC_ALL_DEVICES_SPECIFIER */
#endif /* __APPLE__ */
#endif /* AUDIO */
#include "windows.h"
#include "toxic.h"
#include "execute.h"
#include "misc_tools.h"
#include "groupchat.h"
#include "prompt.h"
#include "toxic_strings.h"
#include "log.h"
#include "line_info.h"
#include "settings.h"
#include "input.h"
#include "help.h"
#include "notify.h"
#include "autocomplete.h"
#include "audio_device.h"
extern char *DATA_FILE;
static GroupChat groupchats[MAX_GROUPCHAT_NUM];
static int max_groupchat_index = 0;
extern struct user_settings *user_settings;
extern struct Winthread Winthread;
#ifdef AUDIO
#define AC_NUM_GROUP_COMMANDS 24
#else
#define AC_NUM_GROUP_COMMANDS 20
#endif /* AUDIO */
/* Array of groupchat command names used for tab completion. */
static const char group_cmd_list[AC_NUM_GROUP_COMMANDS][MAX_CMDNAME_SIZE] = {
{ "/accept" },
{ "/add" },
{ "/avatar" },
{ "/clear" },
{ "/close" },
{ "/connect" },
{ "/decline" },
{ "/exit" },
{ "/group" },
{ "/help" },
{ "/log" },
{ "/myid" },
{ "/myqr" },
{ "/nick" },
{ "/note" },
{ "/nospam" },
{ "/quit" },
{ "/requests" },
{ "/status" },
{ "/title" },
#ifdef AUDIO
{ "/lsdev" },
{ "/sdev" },
{ "/mute" },
{ "/sense" },
#endif /* AUDIO */
};
#ifdef AUDIO
static int group_audio_open_out_device(int groupnum);
static int group_audio_close_out_device(int groupnum);
#endif /* AUDIO */
int init_groupchat_win(ToxWindow *prompt, Tox *m, int groupnum, uint8_t type)
{
if (groupnum > MAX_GROUPCHAT_NUM)
return -1;
ToxWindow self = new_group_chat(m, groupnum);
int i;
for (i = 0; i <= max_groupchat_index; ++i) {
if (!groupchats[i].active) {
groupchats[i].chatwin = add_window(m, self);
groupchats[i].active = true;
groupchats[i].num_peers = 0;
groupchats[i].type = type;
groupchats[i].start_time = get_unix_time();
groupchats[i].peer_names = malloc(sizeof(uint8_t) * TOX_MAX_NAME_LENGTH);
groupchats[i].oldpeer_names = malloc(sizeof(uint8_t) * TOX_MAX_NAME_LENGTH);
groupchats[i].peer_name_lengths = malloc(sizeof(uint16_t));
groupchats[i].oldpeer_name_lengths = malloc(sizeof(uint16_t));
if (groupchats[i].peer_names == NULL || groupchats[i].oldpeer_names == NULL
|| groupchats[i].peer_name_lengths == NULL || groupchats[i].oldpeer_name_lengths == NULL)
exit_toxic_err("failed in init_groupchat_win", FATALERR_MEMORY);
memcpy(&groupchats[i].oldpeer_names[0], UNKNOWN_NAME, sizeof(UNKNOWN_NAME));
groupchats[i].oldpeer_name_lengths[0] = (uint16_t) strlen(UNKNOWN_NAME);
#ifdef AUDIO
if (type == TOX_GROUPCHAT_TYPE_AV)
if (group_audio_open_out_device(i) == -1)
fprintf(stderr, "Group Audio failed to init\n");
#endif /* AUDIO */
set_active_window(groupchats[i].chatwin);
if (i == max_groupchat_index)
++max_groupchat_index;
return 0;
}
}
return -1;
}
static void kill_groupchat_window(ToxWindow *self)
{
ChatContext *ctx = self->chatwin;
log_disable(ctx->log);
line_info_cleanup(ctx->hst);
delwin(ctx->linewin);
delwin(ctx->history);
delwin(ctx->sidebar);
free(ctx->log);
free(ctx);
free(self->help);
del_window(self);
}
void close_groupchat(ToxWindow *self, Tox *m, int groupnum)
{
tox_del_groupchat(m, groupnum);
#ifdef AUDIO
group_audio_close_out_device(groupnum);
#endif
free(groupchats[groupnum].peer_names);
free(groupchats[groupnum].oldpeer_names);
free(groupchats[groupnum].peer_name_lengths);
free(groupchats[groupnum].oldpeer_name_lengths);
memset(&groupchats[groupnum], 0, sizeof(GroupChat));
int i;
for (i = max_groupchat_index; i > 0; --i) {
if (groupchats[i - 1].active)
break;
}
max_groupchat_index = i;
kill_groupchat_window(self);
}
/* destroys and re-creates groupchat window with or without the peerlist */
void redraw_groupchat_win(ToxWindow *self)
{
ChatContext *ctx = self->chatwin;
endwin();
refresh();
clear();
int x2, y2;
getmaxyx(stdscr, y2, x2);
y2 -= 2;
if (y2 <= 0 || x2 <= 0)
return;
if (ctx->sidebar) {
delwin(ctx->sidebar);
ctx->sidebar = NULL;
}
delwin(ctx->linewin);
delwin(ctx->history);
delwin(self->window);
self->window = newwin(y2, x2, 0, 0);
ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - CHATBOX_HEIGHT, 0);
if (self->show_peerlist) {
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, x2 - SIDEBAR_WIDTH - 1, 0, 0);
ctx->sidebar = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, SIDEBAR_WIDTH, 0, x2 - SIDEBAR_WIDTH);
} else {
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, x2, 0, 0);
}
scrollok(ctx->history, 0);
}
static void groupchat_onGroupMessage(ToxWindow *self, Tox *m, int groupnum, int peernum,
const char *msg, uint16_t len)
{
if (self->num != groupnum)
return;
ChatContext *ctx = self->chatwin;
char nick[TOX_MAX_NAME_LENGTH];
get_group_nick_truncate(m, nick, peernum, groupnum);
char selfnick[TOX_MAX_NAME_LENGTH];
tox_self_get_name(m, (uint8_t *) selfnick);
size_t sn_len = tox_self_get_name_size(m);
selfnick[sn_len] = '\0';
int nick_clr = strcmp(nick, selfnick) == 0 ? GREEN : CYAN;
/* Only play sound if mentioned by someone else */
if (strcasestr(msg, selfnick) && strcmp(selfnick, nick)) {
sound_notify(self, generic_message, NT_WNDALERT_0 | user_settings->bell_on_message, NULL);
if (self->active_box != -1)
box_silent_notify2(self, NT_NOFOCUS, self->active_box, "%s %s", nick, msg);
else
box_silent_notify(self, NT_NOFOCUS, &self->active_box, self->name, "%s %s", nick, msg);
nick_clr = RED;
} else {
sound_notify(self, silent, NT_WNDALERT_1, NULL);
}
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
line_info_add(self, timefrmt, nick, NULL, IN_MSG, 0, nick_clr, "%s", msg);
write_to_log(msg, nick, ctx->log, false);
}
static void groupchat_onGroupAction(ToxWindow *self, Tox *m, int groupnum, int peernum, const char *action,
uint16_t len)
{
if (self->num != groupnum)
return;
ChatContext *ctx = self->chatwin;
char nick[TOX_MAX_NAME_LENGTH];
get_group_nick_truncate(m, nick, peernum, groupnum);
char selfnick[TOX_MAX_NAME_LENGTH];
tox_self_get_name(m, (uint8_t *) selfnick);
size_t n_len = tox_self_get_name_size(m);
selfnick[n_len] = '\0';
if (strcasestr(action, selfnick)) {
sound_notify(self, generic_message, NT_WNDALERT_0 | user_settings->bell_on_message, NULL);
if (self->active_box != -1)
box_silent_notify2(self, NT_NOFOCUS, self->active_box, "* %s %s", nick, action );
else
box_silent_notify(self, NT_NOFOCUS, &self->active_box, self->name, "* %s %s", nick, action);
} else {
sound_notify(self, silent, NT_WNDALERT_1, NULL);
}
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
line_info_add(self, timefrmt, nick, NULL, IN_ACTION, 0, 0, "%s", action);
write_to_log(action, nick, ctx->log, true);
}
static void groupchat_onGroupTitleChange(ToxWindow *self, Tox *m, int groupnum, int peernum, const char *title,
uint8_t length)
{
ChatContext *ctx = self->chatwin;
if (self->num != groupnum)
return;
set_window_title(self, title, length);
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
/* don't announce title when we join the room */
if (!timed_out(groupchats[self->num].start_time, GROUP_EVENT_WAIT))
return;
char nick[TOX_MAX_NAME_LENGTH];
get_group_nick_truncate(m, nick, peernum, groupnum);
line_info_add(self, timefrmt, nick, NULL, NAME_CHANGE, 0, 0, " set the group title to: %s", title);
char tmp_event[MAX_STR_SIZE];
snprintf(tmp_event, sizeof(tmp_event), "set title to %s", title);
write_to_log(tmp_event, nick, ctx->log, true);
}
/* Puts two copies of peerlist/lengths in chat instance */
static void copy_peernames(int gnum, uint8_t peerlist[][TOX_MAX_NAME_LENGTH], uint16_t lengths[], int npeers)
{
/* Assumes these are initiated in init_groupchat_win */
free(groupchats[gnum].peer_names);
free(groupchats[gnum].oldpeer_names);
free(groupchats[gnum].peer_name_lengths);
free(groupchats[gnum].oldpeer_name_lengths);
int N = TOX_MAX_NAME_LENGTH;
groupchats[gnum].peer_names = malloc(sizeof(uint8_t) * npeers * N);
groupchats[gnum].oldpeer_names = malloc(sizeof(uint8_t) * npeers * N);
groupchats[gnum].peer_name_lengths = malloc(sizeof(uint16_t) * npeers);
groupchats[gnum].oldpeer_name_lengths = malloc(sizeof(uint16_t) * npeers);
if (groupchats[gnum].peer_names == NULL || groupchats[gnum].oldpeer_names == NULL
|| groupchats[gnum].peer_name_lengths == NULL || groupchats[gnum].oldpeer_name_lengths == NULL) {
exit_toxic_err("failed in copy_peernames", FATALERR_MEMORY);
}
uint16_t u_len = strlen(UNKNOWN_NAME);
int i;
for (i = 0; i < npeers; ++i) {
if (!lengths[i]) {
memcpy(&groupchats[gnum].peer_names[i * N], UNKNOWN_NAME, u_len);
groupchats[gnum].peer_names[i * N + u_len] = '\0';
groupchats[gnum].peer_name_lengths[i] = u_len;
} else {
uint16_t n_len = MIN(lengths[i], TOXIC_MAX_NAME_LENGTH - 1);
memcpy(&groupchats[gnum].peer_names[i * N], peerlist[i], n_len);
groupchats[gnum].peer_names[i * N + n_len] = '\0';
groupchats[gnum].peer_name_lengths[i] = n_len;
filter_str((char *) &groupchats[gnum].peer_names[i * N], n_len);
}
}
memcpy(groupchats[gnum].oldpeer_names, groupchats[gnum].peer_names, N * npeers);
memcpy(groupchats[gnum].oldpeer_name_lengths, groupchats[gnum].peer_name_lengths, sizeof(uint16_t) * npeers);
}
struct group_add_thrd {
Tox *m;
ToxWindow *self;
int peernum;
int groupnum;
time_t timestamp;
pthread_t tid;
pthread_attr_t attr;
};
/* Waits GROUP_EVENT_WAIT seconds for a new peer to set their name before announcing them */
void *group_add_wait(void *data)
{
struct group_add_thrd *thrd = (struct group_add_thrd *) data;
ToxWindow *self = thrd->self;
Tox *m = thrd->m;
char peername[TOX_MAX_NAME_LENGTH];
/* keep polling for a name that differs from the default until we run out of time */
while (true) {
usleep(100000);
pthread_mutex_lock(&Winthread.lock);
get_group_nick_truncate(m, peername, thrd->peernum, thrd->groupnum);
if (strcmp(peername, DEFAULT_TOX_NAME) || timed_out(thrd->timestamp, GROUP_EVENT_WAIT)) {
pthread_mutex_unlock(&Winthread.lock);
break;
}
pthread_mutex_unlock(&Winthread.lock);
}
const char *event = "has joined the room";
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
pthread_mutex_lock(&Winthread.lock);
line_info_add(self, timefrmt, (char *) peername, NULL, CONNECTION, 0, GREEN, event);
write_to_log(event, (char *) peername, self->chatwin->log, true);
pthread_mutex_unlock(&Winthread.lock);
pthread_attr_destroy(&thrd->attr);
free(thrd);
pthread_exit(NULL);
}
static void groupchat_onGroupNamelistChange(ToxWindow *self, Tox *m, int groupnum, int peernum, uint8_t change)
{
if (self->num != groupnum)
return;
if (groupnum > max_groupchat_index)
return;
groupchats[groupnum].num_peers = tox_group_number_peers(m, groupnum);
int num_peers = groupchats[groupnum].num_peers;
if (peernum > num_peers)
return;
/* get old peer name before updating name list */
uint8_t oldpeername[TOX_MAX_NAME_LENGTH];
if (change != TOX_CHAT_CHANGE_PEER_ADD) {
memcpy(oldpeername, &groupchats[groupnum].oldpeer_names[peernum * TOX_MAX_NAME_LENGTH],
sizeof(oldpeername));
uint16_t old_n_len = groupchats[groupnum].oldpeer_name_lengths[peernum];
oldpeername[old_n_len] = '\0';
}
/* Update name/len lists */
uint8_t tmp_peerlist[num_peers][TOX_MAX_NAME_LENGTH];
uint16_t tmp_peerlens[num_peers];
if (tox_group_get_names(m, groupnum, tmp_peerlist, tmp_peerlens, num_peers) == -1) {
memset(tmp_peerlist, 0, sizeof(tmp_peerlist));
memset(tmp_peerlens, 0, sizeof(tmp_peerlens));
}
copy_peernames(groupnum, tmp_peerlist, tmp_peerlens, num_peers);
/* get current peername then sort namelist */
uint8_t peername[TOX_MAX_NAME_LENGTH];
if (change != TOX_CHAT_CHANGE_PEER_DEL) {
uint16_t n_len = groupchats[groupnum].peer_name_lengths[peernum];
memcpy(peername, &groupchats[groupnum].peer_names[peernum * TOX_MAX_NAME_LENGTH], sizeof(peername));
peername[n_len] = '\0';
}
qsort(groupchats[groupnum].peer_names, groupchats[groupnum].num_peers, TOX_MAX_NAME_LENGTH, qsort_strcasecmp_hlpr);
ChatContext *ctx = self->chatwin;
const char *event;
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
switch (change) {
case TOX_CHAT_CHANGE_PEER_ADD:
if (!timed_out(groupchats[groupnum].start_time, GROUP_EVENT_WAIT))
break;
struct group_add_thrd *thrd = malloc(sizeof(struct group_add_thrd));
thrd->m = m;
thrd->peernum = peernum;
thrd->groupnum = groupnum;
thrd->self = self;
thrd->timestamp = get_unix_time();
if (pthread_attr_init(&thrd->attr) != 0) {
free(thrd);
return;
}
if (pthread_attr_setdetachstate(&thrd->attr, PTHREAD_CREATE_DETACHED) != 0) {
pthread_attr_destroy(&thrd->attr);
free(thrd);
return;
}
if (pthread_create(&thrd->tid, &thrd->attr, group_add_wait, (void *) thrd) != 0) {
pthread_attr_destroy(&thrd->attr);
free(thrd);
return;
}
break;
case TOX_CHAT_CHANGE_PEER_DEL:
event = "has left the room";
line_info_add(self, timefrmt, (char *) oldpeername, NULL, DISCONNECTION, 0, RED, event);
if (groupchats[self->num].side_pos > 0)
--groupchats[self->num].side_pos;
write_to_log(event, (char *) oldpeername, ctx->log, true);
break;
case TOX_CHAT_CHANGE_PEER_NAME:
if (!timed_out(groupchats[self->num].start_time, GROUP_EVENT_WAIT))
return;
/* ignore initial name change (TODO: this is a bad way to do this) */
if (strcmp((char *) oldpeername, DEFAULT_TOX_NAME) == 0)
return;
event = " is now known as ";
line_info_add(self, timefrmt, (char *) oldpeername, (char *) peername, NAME_CHANGE, 0, 0, event);
char tmp_event[TOXIC_MAX_NAME_LENGTH * 2 + 32];
snprintf(tmp_event, sizeof(tmp_event), "is now known as %s", (char *) peername);
write_to_log(tmp_event, (char *) oldpeername, ctx->log, true);
break;
}
sound_notify(self, silent, NT_WNDALERT_2, NULL);
}
static void send_group_action(ToxWindow *self, ChatContext *ctx, Tox *m, char *action)
{
if (action == NULL) {
wprintw(ctx->history, "Invalid syntax.\n");
return;
}
if (tox_group_action_send(m, self->num, (uint8_t *) action, strlen(action)) == -1) {
const char *errmsg = " * Failed to send action.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, errmsg);
}
}
static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
{
ChatContext *ctx = self->chatwin;
int x, y, y2, x2;
getyx(self->window, y, x);
getmaxyx(self->window, y2, x2);
if (x2 <= 0 || y2 <= 0)
return;
if (self->help->active) {
help_onKey(self, key);
return;
}
if (ctx->pastemode && key == '\r')
key = '\n';
if (ltr || key == '\n') { /* char is printable */
input_new_char(self, key, x, y, x2, y2);
return;
}
if (line_info_onKey(self, key))
return;
if (input_handle(self, key, x, y, x2, y2))
return;
if (key == '\t') { /* TAB key: auto-completes peer name or command */
if (ctx->len > 0) {
int diff;
/* TODO: make this not suck */
if (ctx->line[0] != L'/' || wcscmp(ctx->line, L"/me") == 0) {
diff = complete_line(self, groupchats[self->num].peer_names, groupchats[self->num].num_peers,
TOX_MAX_NAME_LENGTH);
} else if (wcsncmp(ctx->line, L"/avatar \"", wcslen(L"/avatar \"")) == 0) {
diff = dir_match(self, m, ctx->line, L"/avatar");
} else {
diff = complete_line(self, group_cmd_list, AC_NUM_GROUP_COMMANDS, MAX_CMDNAME_SIZE);
}
if (diff != -1) {
if (x + diff > x2 - 1) {
int wlen = MAX(0, wcswidth(ctx->line, sizeof(ctx->line) / sizeof(wchar_t)));
ctx->start = wlen < x2 ? 0 : wlen - x2 + 1;
}
} else {
sound_notify(self, notif_error, 0, NULL);
}
} else {
sound_notify(self, notif_error, 0, NULL);
}
} else if (key == user_settings->key_peer_list_down) { /* Scroll peerlist up and down one position */
int L = y2 - CHATBOX_HEIGHT - SDBAR_OFST;
if (groupchats[self->num].side_pos < groupchats[self->num].num_peers - L)
++groupchats[self->num].side_pos;
} else if (key == user_settings->key_peer_list_up) {
if (groupchats[self->num].side_pos > 0)
--groupchats[self->num].side_pos;
} else if (key == '\r') {
rm_trailing_spaces_buf(ctx);
if (!wstring_is_empty(ctx->line)) {
add_line_to_hist(ctx);
wstrsubst(ctx->line, L'', L'\n');
char line[MAX_STR_SIZE];
if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1)
memset(&line, 0, sizeof(line));
if (line[0] == '/') {
if (strcmp(line, "/close") == 0) {
close_groupchat(self, m, self->num);
return;
} else if (strncmp(line, "/me ", strlen("/me ")) == 0) {
send_group_action(self, ctx, m, line + strlen("/me "));
} else {
execute(ctx->history, self, m, line, GROUPCHAT_COMMAND_MODE);
}
} else {
if (tox_group_message_send(m, self->num, (uint8_t *) line, strlen(line)) == -1) {
const char *errmsg = " * Failed to send message.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, errmsg);
}
}
}
wclear(ctx->linewin);
wmove(self->window, y2 - CURS_Y_OFFSET, 0);
reset_buf(ctx);
}
}
static void groupchat_onDraw(ToxWindow *self, Tox *m)
{
int x2, y2;
getmaxyx(self->window, y2, x2);
if (x2 <= 0 || y2 <= 0)
return;
ChatContext *ctx = self->chatwin;
pthread_mutex_lock(&Winthread.lock);
line_info_print(self);
pthread_mutex_unlock(&Winthread.lock);
wclear(ctx->linewin);
curs_set(1);
if (ctx->len > 0)
mvwprintw(ctx->linewin, 1, 0, "%ls", &ctx->line[ctx->start]);
wclear(ctx->sidebar);
mvwhline(self->window, y2 - CHATBOX_HEIGHT, 0, ACS_HLINE, x2);
if (self->show_peerlist) {
mvwvline(ctx->sidebar, 0, 0, ACS_VLINE, y2 - CHATBOX_HEIGHT);
mvwaddch(ctx->sidebar, y2 - CHATBOX_HEIGHT, 0, ACS_BTEE);
pthread_mutex_lock(&Winthread.lock);
int num_peers = groupchats[self->num].num_peers;
pthread_mutex_unlock(&Winthread.lock);
wmove(ctx->sidebar, 0, 1);
wattron(ctx->sidebar, A_BOLD);
wprintw(ctx->sidebar, "Peers: %d\n", num_peers);
wattroff(ctx->sidebar, A_BOLD);
mvwaddch(ctx->sidebar, 1, 0, ACS_LTEE);
mvwhline(ctx->sidebar, 1, 1, ACS_HLINE, SIDEBAR_WIDTH - 1);
int maxlines = y2 - SDBAR_OFST - CHATBOX_HEIGHT;
int i;
for (i = 0; i < num_peers && i < maxlines; ++i) {
wmove(ctx->sidebar, i + 2, 1);
pthread_mutex_lock(&Winthread.lock);
int peer = i + groupchats[self->num].side_pos;
pthread_mutex_unlock(&Winthread.lock);
/* truncate nick to fit in side panel without modifying list */
char tmpnck[TOX_MAX_NAME_LENGTH];
int maxlen = SIDEBAR_WIDTH - 2;
pthread_mutex_lock(&Winthread.lock);
memcpy(tmpnck, &groupchats[self->num].peer_names[peer * TOX_MAX_NAME_LENGTH], maxlen);
pthread_mutex_unlock(&Winthread.lock);
tmpnck[maxlen] = '\0';
wprintw(ctx->sidebar, "%s\n", tmpnck);
}
}
int y, x;
getyx(self->window, y, x);
(void) x;
int new_x = ctx->start ? x2 - 1 : MAX(0, wcswidth(ctx->line, ctx->pos));
wmove(self->window, y + 1, new_x);
wrefresh(self->window);
if (self->help->active)
help_onDraw(self);
}
static void groupchat_onInit(ToxWindow *self, Tox *m)
{
int x2, y2;
getmaxyx(self->window, y2, x2);
if (x2 <= 0 || y2 <= 0)
exit_toxic_err("failed in groupchat_onInit", FATALERR_CURSES);
ChatContext *ctx = self->chatwin;
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, x2 - SIDEBAR_WIDTH - 1, 0, 0);
ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - CHATBOX_HEIGHT, 0);
ctx->sidebar = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, SIDEBAR_WIDTH, 0, x2 - SIDEBAR_WIDTH);
ctx->hst = calloc(1, sizeof(struct history));
ctx->log = calloc(1, sizeof(struct chatlog));
if (ctx->log == NULL || ctx->hst == NULL)
exit_toxic_err("failed in groupchat_onInit", FATALERR_MEMORY);
line_info_init(ctx->hst);
if (user_settings->autolog == AUTOLOG_ON) {
char myid[TOX_ADDRESS_SIZE];
tox_self_get_address(m, (uint8_t *) myid);
if (log_enable(self->name, myid, NULL, ctx->log, LOG_GROUP) == -1)
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Warning: Log failed to initialize.");
}
execute(ctx->history, self, m, "/log", GLOBAL_COMMAND_MODE);
scrollok(ctx->history, 0);
wmove(self->window, y2 - CURS_Y_OFFSET, 0);
}
#ifdef AUDIO
static int group_audio_open_out_device(int groupnum)
{
char dname[MAX_STR_SIZE];
get_primary_device_name(output, dname, sizeof(dname));
dname[MAX_STR_SIZE - 1] = '\0';
groupchats[groupnum].audio.dvhandle = alcOpenDevice(dname);
if (groupchats[groupnum].audio.dvhandle == NULL)
return -1;
groupchats[groupnum].audio.dvctx = alcCreateContext(groupchats[groupnum].audio.dvhandle, NULL);
alcMakeContextCurrent(groupchats[groupnum].audio.dvctx);
alGenBuffers(OPENAL_BUFS, groupchats[groupnum].audio.buffers);
alGenSources((uint32_t) 1, &groupchats[groupnum].audio.source);
alSourcei(groupchats[groupnum].audio.source, AL_LOOPING, AL_FALSE);
if (alcGetError(groupchats[groupnum].audio.dvhandle) != AL_NO_ERROR) {
group_audio_close_out_device(groupnum);
groupchats[groupnum].audio.dvhandle = NULL;
groupchats[groupnum].audio.dvctx = NULL;
return -1;
}
alSourceQueueBuffers(groupchats[groupnum].audio.source, OPENAL_BUFS, groupchats[groupnum].audio.buffers);
alSourcePlay(groupchats[groupnum].audio.source);
return 0;
}
static int group_audio_close_out_device(int groupnum)
{
if (!groupchats[groupnum].audio.dvhandle)
return -1;
if (!groupchats[groupnum].audio.dvctx)
return -1;
if (alcGetCurrentContext() != groupchats[groupnum].audio.dvctx)
alcMakeContextCurrent(groupchats[groupnum].audio.dvctx);
alDeleteSources((uint32_t) 1, &groupchats[groupnum].audio.source);
alDeleteBuffers(OPENAL_BUFS, groupchats[groupnum].audio.buffers);
alcMakeContextCurrent(NULL);
alcDestroyContext(groupchats[groupnum].audio.dvctx);
if (!alcCloseDevice(groupchats[groupnum].audio.dvhandle))
return -1;
return 0;
}
// static int group_audio_write(int peernum, int groupnum, const int16_t *pcm, unsigned int samples, uint8_t channels,
// unsigned int sample_rate)
// {
// if (!pcm)
// return -1;
// if (channels == 0 || channels > 2)
// return -2;
// ALuint bufid;
// ALint processed = 0, queued = 0;
// alGetSourcei(groupchats[groupnum].audio.source, AL_BUFFERS_PROCESSED, &processed);
// alGetSourcei(groupchats[groupnum].audio.source, AL_BUFFERS_QUEUED, &queued);
// fprintf(stderr, "source: %d, queued: %d, processed: %d\n", groupchats[groupnum].audio.source, queued, processed);
// if (processed) {
// ALuint bufids[processed];
// alSourceUnqueueBuffers(groupchats[groupnum].audio.source, processed, bufids);
// alDeleteBuffers(processed - 1, bufids + 1);
// bufid = bufids[0];
// } else if (queued < 16) {
// alGenBuffers(1, &bufid);
// } else {
// return -3;
// }
// int length = samples * channels * sizeof(int16_t);
// alBufferData(bufid, (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, pcm, length, sample_rate);
// alSourceQueueBuffers(groupchats[groupnum].audio.source, 1, &bufid);
// ALint state;
// alGetSourcei(groupchats[groupnum].audio.source, AL_SOURCE_STATE, &state);
// if (state != AL_PLAYING)
// alSourcePlay(groupchats[groupnum].audio.source);
// return 0;
// }
static void groupchat_onWriteDevice(ToxWindow *self, Tox *m, uint32_t groupnum, int peernum, const int16_t *pcm,
unsigned int samples, uint8_t channels, unsigned int sample_rate)
{
return;
// if (groupnum != self->num)
// return;
// if (peernum < 0)
// return;
// if (groupchats[groupnum].audio.dvhandle == NULL)
// fprintf(stderr, "dvhandle is null)\n");
// if (groupchats[groupnum].audio.dvctx == NULL)
// fprintf(stderr, "ctx is null\n");
// int ret = group_audio_write(peernum, groupnum, pcm, samples, channels, sample_rate);
// fprintf(stderr, "write: %d\n", ret);
}
#endif /* AUDIO */
ToxWindow new_group_chat(Tox *m, int groupnum)
{
ToxWindow ret;
memset(&ret, 0, sizeof(ret));
ret.active = true;
ret.is_groupchat = true;
ret.onKey = &groupchat_onKey;
ret.onDraw = &groupchat_onDraw;
ret.onInit = &groupchat_onInit;
ret.onGroupMessage = &groupchat_onGroupMessage;
ret.onGroupNamelistChange = &groupchat_onGroupNamelistChange;
ret.onGroupAction = &groupchat_onGroupAction;
ret.onGroupTitleChange = &groupchat_onGroupTitleChange;
#ifdef AUDIO
ret.onWriteDevice = &groupchat_onWriteDevice;
#endif
snprintf(ret.name, sizeof(ret.name), "Group %d", groupnum);
ChatContext *chatwin = calloc(1, sizeof(ChatContext));
Help *help = calloc(1, sizeof(Help));
if (chatwin == NULL || help == NULL)
exit_toxic_err("failed in new_group_chat", FATALERR_MEMORY);
ret.chatwin = chatwin;
ret.help = help;
ret.num = groupnum;
ret.show_peerlist = true;
ret.active_box = -1;
return ret;
}

View File

@ -1,86 +0,0 @@
/* groupchat.h
*
*
* Copyright (C) 2014 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GROUPCHAT_H
#define GROUPCHAT_H
#include "toxic.h"
#include "windows.h"
#ifdef AUDIO
#include "audio_call.h"
#endif
#ifdef AUDIO
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
/* compatibility with older versions of OpenAL */
#ifndef ALC_ALL_DEVICES_SPECIFIER
#include <AL/alext.h>
#endif /* ALC_ALL_DEVICES_SPECIFIER */
#endif /* __APPLE__ */
#endif /* AUDIO */
#define SIDEBAR_WIDTH 16
#define SDBAR_OFST 2 /* Offset for the peer number box at the top of the statusbar */
#define MAX_GROUPCHAT_NUM MAX_WINDOWS_NUM - 2
#define GROUP_EVENT_WAIT 3
#ifdef AUDIO
struct GAudio {
ALCdevice *dvhandle; /* Handle of device selected/opened */
ALCcontext *dvctx;
ALuint source;
ALuint buffers[OPENAL_BUFS];
};
#endif /* AUDIO */
typedef struct {
int chatwin;
bool active;
uint8_t type;
int num_peers;
int side_pos; /* current position of the sidebar - used for scrolling up and down */
time_t start_time;
uint8_t *peer_names;
uint8_t *oldpeer_names;
uint16_t *peer_name_lengths;
uint16_t *oldpeer_name_lengths;
#ifdef AUDIO
struct GAudio audio;
#endif
} GroupChat;
void close_groupchat(ToxWindow *self, Tox *m, int groupnum);
int init_groupchat_win(ToxWindow *prompt, Tox *m, int groupnum, uint8_t type);
/* destroys and re-creates groupchat window with or without the peerlist */
void redraw_groupchat_win(ToxWindow *self);
ToxWindow new_group_chat(Tox *m, int groupnum);
#endif /* #define GROUPCHAT_H */

View File

@ -22,24 +22,34 @@
#include <string.h>
#include "windows.h"
#include "toxic.h"
#include "help.h"
#include "misc_tools.h"
#include "toxic.h"
#include "windows.h"
#ifdef PYTHON
#include "api.h"
#endif /* PYTHON */
#ifdef PYTHON
#define HELP_MENU_HEIGHT 10
#else
#define HELP_MENU_HEIGHT 9
#endif /* PYTHON */
#define HELP_MENU_WIDTH 26
void help_init_menu(ToxWindow *self)
{
if (self->help->win)
if (self->help->win) {
delwin(self->help->win);
}
int y2, x2;
getmaxyx(self->window, y2, x2);
if (y2 < HELP_MENU_HEIGHT || x2 < HELP_MENU_WIDTH)
if (y2 < HELP_MENU_HEIGHT || x2 < HELP_MENU_WIDTH) {
return;
}
self->help->win = newwin(HELP_MENU_HEIGHT, HELP_MENU_WIDTH, 3, 3);
self->help->active = true;
@ -49,19 +59,24 @@ void help_init_menu(ToxWindow *self)
static void help_exit(ToxWindow *self)
{
delwin(self->help->win);
memset(self->help, 0, sizeof(Help));
*(self->help) = (struct Help) {
0
};
}
static void help_init_window(ToxWindow *self, int height, int width)
{
if (self->help->win)
if (self->help->win) {
delwin(self->help->win);
}
int y2, x2;
getmaxyx(stdscr, y2, x2);
if (y2 <= 0 || x2 <= 0)
if (y2 <= 0 || x2 <= 0) {
return;
}
height = MIN(height, y2);
width = MIN(width, x2);
@ -89,11 +104,18 @@ static void help_draw_menu(ToxWindow *self)
wattroff(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, "hat commands\n");
wprintw(win, " g");
wprintw(win, " c");
wattron(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, "r");
wprintw(win, "o");
wattroff(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, "oup commands\n");
wprintw(win, "nference commands\n");
#ifdef PYTHON
wattron(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, " p");
wattroff(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, "lugin commands\n");
#endif /* PYTHON */
wattron(win, A_BOLD | COLOR_PAIR(BLUE));
wprintw(win, " f");
@ -112,14 +134,15 @@ static void help_draw_menu(ToxWindow *self)
wprintw(win, "it menu\n");
box(win, ACS_VLINE, ACS_HLINE);
wrefresh(win);
wnoutrefresh(win);
}
static void help_draw_bottom_menu(WINDOW *win)
{
int y2, x2;
getmaxyx(win, y2, x2);
(void) x2;
UNUSED_VAR(x2);
wmove(win, y2 - 2, 1);
@ -151,18 +174,24 @@ static void help_draw_global(ToxWindow *self)
wprintw(win, " /decline <id> : Decline friend request\n");
wprintw(win, " /requests : List pending friend requests\n");
wprintw(win, " /connect <ip> <port> <key> : Manually connect to a DHT node\n");
wprintw(win, " /status <type> <msg> : Set status with optional note\n");
wprintw(win, " /status <type> : Set status (Online, Busy, Away)\n");
wprintw(win, " /note <msg> : Set a personal note\n");
wprintw(win, " /nick <nick> : Set your nickname\n");
wprintw(win, " /nospam <value> : Change part of your Tox ID to stop spam\n");
wprintw(win, " /log <on> or <off> : Enable/disable logging\n");
wprintw(win, " /group <type> : Create a group chat where type: text | audio\n");
wprintw(win, " /conference <type> : Create a conference where type: text | audio\n");
wprintw(win, " /myid : Print your Tox ID\n");
#ifdef GAMES
wprintw(win, " /game : Play a game\n");
#endif /* GAMES */
#ifdef QRCODE
#ifdef QRPNG
wprintw(win, " /myqr <txt> or <png> : Print your Tox ID's QR code to a file.\n");
#else
wprintw(win, " /myqr : Print your Tox ID's QR code to a file.\n");
#endif /* QRPNG */
#endif /* QRCODE */
wprintw(win, " /clear : Clear window history\n");
wprintw(win, " /close : Close the current chat window\n");
wprintw(win, " /quit or /exit : Exit Toxic\n");
@ -185,10 +214,18 @@ static void help_draw_global(ToxWindow *self)
wprintw(win, " /svdev <type> <id> : Set active video device\n");
#endif /* VIDEO */
#ifdef PYTHON
wattron(win, A_BOLD);
wprintw(win, "\n Scripting:\n");
wattroff(win, A_BOLD);
wprintw(win, " /run <path> : Load and run the script at path\n");
#endif /* PYTHON */
help_draw_bottom_menu(win);
box(win, ACS_VLINE, ACS_HLINE);
wrefresh(win);
wnoutrefresh(win);
}
static void help_draw_chat(ToxWindow *self)
@ -201,8 +238,8 @@ static void help_draw_chat(ToxWindow *self)
wprintw(win, "Chat Commands:\n");
wattroff(win, A_BOLD | COLOR_PAIR(RED));
wprintw(win, " /invite <n> : Invite contact to a group chat\n");
wprintw(win, " /join : Join a pending group chat\n");
wprintw(win, " /invite <n> : Invite contact to a conference \n");
wprintw(win, " /join : Join a pending conference\n");
wprintw(win, " /sendfile <path> : Send a file\n");
wprintw(win, " /savefile <id> : Receive a file\n");
wprintw(win, " /cancel <type> <id> : Cancel file transfer where type: in|out\n");
@ -219,19 +256,26 @@ static void help_draw_chat(ToxWindow *self)
wprintw(win, " /sdev <type> <id> : Change active device\n");
wprintw(win, " /mute <type> : Mute active device if in call\n");
wprintw(win, " /sense <n> : VAD sensitivity threshold\n");
wprintw(win, " /bitrate <n> : Set the audio encoding bitrate\n");
#endif /* AUDIO */
#ifdef VIDEO
wattron(win, A_BOLD);
wprintw(win, "\n Video:\n");
wattroff(win, A_BOLD);
wprintw(win, " /video : Toggle video call\n");
wprintw(win, " /res <width> <height> : Set video resolution\n");
wprintw(win, " /vcall : Video call\n");
wprintw(win, " /video : Toggle video in call\n");
#endif /* VIDEO */
#ifdef GAMES
wprintw(win, " /game : Play a game with contact\n");
#endif /* GAMES */
help_draw_bottom_menu(win);
box(win, ACS_VLINE, ACS_HLINE);
wrefresh(win);
wnoutrefresh(win);
}
static void help_draw_keys(ToxWindow *self)
@ -248,8 +292,8 @@ static void help_draw_keys(ToxWindow *self)
wprintw(win, " Page Up and Page Down : Scroll window history one line\n");
wprintw(win, " Ctrl+F and Ctrl+V : Scroll window history half a page\n");
wprintw(win, " Ctrl+H : Move to the bottom of window history\n");
wprintw(win, " Ctrl+[ and Ctrl+] : Scroll peer list in groupchats\n");
wprintw(win, " Ctrl+B : Toggle the groupchat peerlist\n");
wprintw(win, " Ctrl+up and Ctrl+down : Scroll peer list in conference\n");
wprintw(win, " Ctrl+B : Toggle the conference peerlist\n");
wprintw(win, " Ctrl+J : Insert new line\n");
wprintw(win, " Ctrl+T : Toggle paste mode\n\n");
wprintw(win, " (Note: Custom keybindings override these defaults.)\n\n");
@ -257,27 +301,57 @@ static void help_draw_keys(ToxWindow *self)
help_draw_bottom_menu(win);
box(win, ACS_VLINE, ACS_HLINE);
wrefresh(win);
wnoutrefresh(win);
}
static void help_draw_group(ToxWindow *self)
static void help_draw_conference(ToxWindow *self)
{
WINDOW *win = self->help->win;
wmove(win, 1, 1);
wattron(win, A_BOLD | COLOR_PAIR(RED));
wprintw(win, "Group commands:\n");
wprintw(win, "Conference commands:\n");
wattroff(win, A_BOLD | COLOR_PAIR(RED));
wprintw(win, " /title <msg> : Set group title (show current title if no msg)\n\n");
wprintw(win, " /title <msg> : Show/set conference title\n");
#ifdef AUDIO
wattron(win, A_BOLD);
wprintw(win, "\n Audio:\n");
wattroff(win, A_BOLD);
wprintw(win, " /audio <on> or <off> : Enable/disable audio in an audio conference\n");
wprintw(win, " /mute : Toggle self audio mute status\n");
wprintw(win, " /mute <nick> or <pubkey> : Toggle peer audio mute status\n");
wprintw(win, " /ptt <on> or <off> : Toggle audio input Push-To-Talk (F2 to activate)\n");
wprintw(win, " /sense <n> : VAD sensitivity threshold\n\n");
#endif
help_draw_bottom_menu(win);
box(win, ACS_VLINE, ACS_HLINE);
wrefresh(win);
wnoutrefresh(win);
}
#ifdef PYTHON
static void help_draw_plugin(ToxWindow *self)
{
WINDOW *win = self->help->win;
wmove(win, 1, 1);
wattron(win, A_BOLD | COLOR_PAIR(RED));
wprintw(win, "Plugin commands:\n");
wattroff(win, A_BOLD | COLOR_PAIR(RED));
draw_handler_help(win);
help_draw_bottom_menu(win);
box(win, ACS_VLINE, ACS_HLINE);
wnoutrefresh(win);
}
#endif /* PYTHON */
static void help_draw_contacts(ToxWindow *self)
{
WINDOW *win = self->help->win;
@ -297,55 +371,75 @@ static void help_draw_contacts(ToxWindow *self)
help_draw_bottom_menu(win);
box(win, ACS_VLINE, ACS_HLINE);
wrefresh(win);
wnoutrefresh(win);
}
void help_onKey(ToxWindow *self, wint_t key)
{
int height;
switch (key) {
case 'x':
case L'x':
case T_KEY_ESC:
help_exit(self);
break;
case 'c':
case L'c':
#ifdef VIDEO
help_init_window(self, 22, 80);
help_init_window(self, 26, 80);
#elif AUDIO
help_init_window(self, 19, 80);
help_init_window(self, 21, 80);
#else
help_init_window(self, 10, 80);
help_init_window(self, 11, 80);
#endif
self->help->type = HELP_CHAT;
break;
case 'g':
case L'g':
height = 22;
#ifdef VIDEO
help_init_window(self, 30, 80);
height += 8;
#elif AUDIO
help_init_window(self, 26, 80);
#else
help_init_window(self, 22, 80);
height += 4;
#endif
#ifdef PYTHON
height += 2;
#endif
#ifdef GAMES
height += 1;
#endif
help_init_window(self, height, 80);
self->help->type = HELP_GLOBAL;
break;
case 'r':
help_init_window(self, 6, 80);
self->help->type = HELP_GROUP;
case L'o':
height = 6;
#ifdef AUDIO
height += 7;
#endif
help_init_window(self, height, 80);
self->help->type = HELP_CONFERENCE;
break;
case 'f':
#ifdef PYTHON
case L'p':
help_init_window(self, 4 + num_registered_handlers(), help_max_width());
self->help->type = HELP_PLUGIN;
break;
#endif /* PYTHON */
case L'f':
help_init_window(self, 10, 80);
self->help->type = HELP_CONTACTS;
break;
case 'k':
case L'k':
help_init_window(self, 15, 80);
self->help->type = HELP_KEYS;
break;
case 'm':
case L'm':
help_init_menu(self);
self->help->type = HELP_MENU;
break;
@ -354,8 +448,6 @@ void help_onKey(ToxWindow *self, wint_t key)
void help_onDraw(ToxWindow *self)
{
curs_set(0);
switch (self->help->type) {
case HELP_MENU:
help_draw_menu(self);
@ -377,8 +469,15 @@ void help_onDraw(ToxWindow *self)
help_draw_contacts(self);
break;
case HELP_GROUP:
help_draw_group(self);
case HELP_CONFERENCE:
help_draw_conference(self);
break;
#ifdef PYTHON
case HELP_PLUGIN:
help_draw_plugin(self);
break;
#endif /* PYTHON */
}
}

View File

@ -30,13 +30,16 @@ typedef enum {
HELP_MENU,
HELP_GLOBAL,
HELP_CHAT,
HELP_GROUP,
HELP_CONFERENCE,
HELP_KEYS,
HELP_CONTACTS,
#ifdef PYTHON
HELP_PLUGIN,
#endif
} HELP_TYPES;
void help_onDraw(ToxWindow *self);
void help_init_menu(ToxWindow *self);
void help_onKey(ToxWindow *self, wint_t key);
#endif /* #define HELP_H */
#endif /* HELP_H */

View File

@ -26,25 +26,26 @@
#include <wchar.h>
#include "toxic.h"
#include "windows.h"
#include "misc_tools.h"
#include "toxic_strings.h"
#include "conference.h"
#include "line_info.h"
#include "misc_tools.h"
#include "notify.h"
#include "groupchat.h"
#include "settings.h"
#include "toxic.h"
#include "toxic_strings.h"
#include "windows.h"
extern struct user_settings *user_settings;
/* add a char to input field and buffer */
void input_new_char(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y)
void input_new_char(ToxWindow *self, wint_t key, int x, int mx_x)
{
ChatContext *ctx = self->chatwin;
/* this is the only place we need to do this check */
if (key == '\n')
if (key == '\n') {
key = L'';
}
int cur_len = wcwidth(key);
@ -77,42 +78,45 @@ static void input_backspace(ToxWindow *self, int x, int mx_x)
int cur_len = ctx->pos > 0 ? wcwidth(ctx->line[ctx->pos - 1]) : 0;
int s_len = ctx->start > 0 ? wcwidth(ctx->line[ctx->start - 1]) : 0;
if (ctx->start && (x >= mx_x - cur_len))
if (ctx->start && (x >= mx_x - cur_len)) {
ctx->start = MAX(0, ctx->start - 1 + (s_len - cur_len));
else if (ctx->start)
} else if (ctx->start) {
ctx->start = MAX(0, ctx->start - cur_len);
}
}
/* delete a char via delete key from input field and buffer */
static void input_delete(ToxWindow *self)
{
if (del_char_buf_frnt(self->chatwin) == -1)
if (del_char_buf_frnt(self->chatwin) == -1) {
sound_notify(self, notif_error, 0, NULL);
}
}
/* delete last typed word */
static void input_del_word(ToxWindow *self, int x, int mx_x)
static void input_del_word(ToxWindow *self)
{
ChatContext *ctx = self->chatwin;
if (del_word_buf(ctx) == -1) {
sound_notify(self, notif_error, 0, NULL);
return;
}
}
/* deletes entire line before cursor from input field and buffer */
static void input_discard(ToxWindow *self)
{
if (discard_buf(self->chatwin) == -1)
if (discard_buf(self->chatwin) == -1) {
sound_notify(self, notif_error, 0, NULL);
}
}
/* deletes entire line after cursor from input field and buffer */
static void input_kill(ChatContext *ctx)
{
if (kill_buf(ctx) == -1)
if (kill_buf(ctx) == -1) {
sound_notify(NULL, notif_error, NT_ALWAYS, NULL);
}
}
static void input_yank(ToxWindow *self, int x, int mx_x)
@ -134,7 +138,7 @@ static void input_yank(ToxWindow *self, int x, int mx_x)
}
/* moves cursor/line position to end of line in input field and buffer */
static void input_mv_end(ToxWindow *self, int y, int mx_x)
static void input_mv_end(ToxWindow *self, int mx_x)
{
ChatContext *ctx = self->chatwin;
@ -149,8 +153,9 @@ static void input_mv_home(ToxWindow *self)
{
ChatContext *ctx = self->chatwin;
if (ctx->pos <= 0)
if (ctx->pos <= 0) {
return;
}
ctx->pos = 0;
ctx->start = 0;
@ -161,18 +166,44 @@ static void input_mv_left(ToxWindow *self, int x, int mx_x)
{
ChatContext *ctx = self->chatwin;
if (ctx->pos <= 0)
if (ctx->pos <= 0) {
return;
}
int cur_len = ctx->pos > 0 ? wcwidth(ctx->line[ctx->pos - 1]) : 0;
int s_len = ctx->start > 0 ? wcwidth(ctx->line[ctx->start - 1]) : 0;
--ctx->pos;
if (ctx->start && (x >= mx_x - cur_len))
if (ctx->start > 0 && (x >= mx_x - cur_len)) {
int s_len = wcwidth(ctx->line[ctx->start - 1]);
ctx->start = MAX(0, ctx->start - 1 + (s_len - cur_len));
else if (ctx->start)
} else if (ctx->start > 0) {
ctx->start = MAX(0, ctx->start - cur_len);
}
}
/* moves the cursor to the beginning of the previous word in input field and buffer */
static void input_skip_left(ToxWindow *self, int x, int mx_x)
{
ChatContext *ctx = self->chatwin;
if (ctx->pos <= 0) {
return;
}
int count = 0;
do {
--ctx->pos;
count += wcwidth(ctx->line[ctx->pos]);
} while (ctx->pos > 0 && (ctx->line[ctx->pos - 1] != L' ' || ctx->line[ctx->pos] == L' '));
if (ctx->start > 0 && (x >= mx_x - count)) {
int s_len = wcwidth(ctx->line[ctx->start - 1]);
ctx->start = MAX(0, ctx->start - 1 + (s_len - count));
} else if (ctx->start > 0) {
ctx->start = MAX(0, ctx->start - count);
}
}
/* moves cursor/line position right in input field and buffer */
@ -180,8 +211,9 @@ static void input_mv_right(ToxWindow *self, int x, int mx_x)
{
ChatContext *ctx = self->chatwin;
if (ctx->pos >= ctx->len)
if (ctx->pos >= ctx->len) {
return;
}
++ctx->pos;
@ -193,6 +225,29 @@ static void input_mv_right(ToxWindow *self, int x, int mx_x)
}
}
/* moves the cursor to the end of the next word in input field and buffer */
static void input_skip_right(ToxWindow *self, int x, int mx_x)
{
ChatContext *ctx = self->chatwin;
if (ctx->pos >= ctx->len) {
return;
}
int count = 0;
do {
count += wcwidth(ctx->line[ctx->pos]);
++ctx->pos;
} while (ctx->pos < ctx->len && !(ctx->line[ctx->pos] == L' ' && ctx->line[ctx->pos - 1] != L' '));
int newpos = x + count;
if (newpos >= mx_x) {
ctx->start += (1 + (newpos - mx_x));
}
}
/* puts a line history item in input field and buffer */
static void input_history(ToxWindow *self, wint_t key, int mx_x)
{
@ -205,7 +260,7 @@ static void input_history(ToxWindow *self, wint_t key, int mx_x)
/* Handles non-printable input keys that behave the same for all types of chat windows.
return true if key matches a function, false otherwise */
bool input_handle(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y)
bool input_handle(ToxWindow *self, wint_t key, int x, int mx_x)
{
bool match = true;
@ -232,7 +287,7 @@ bool input_handle(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y)
break;
case T_KEY_C_W:
input_del_word(self, x, mx_x);
input_del_word(self);
break;
case KEY_HOME:
@ -242,7 +297,7 @@ bool input_handle(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y)
case KEY_END:
case T_KEY_C_E:
input_mv_end(self, y, mx_x);
input_mv_end(self, mx_x);
break;
case KEY_LEFT:
@ -262,6 +317,14 @@ bool input_handle(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y)
force_refresh(self->chatwin->history);
break;
case T_KEY_C_LEFT:
input_skip_left(self, x, mx_x);
break;
case T_KEY_C_RIGHT:
input_skip_right(self, x, mx_x);
break;
default:
match = false;
break;
@ -271,9 +334,9 @@ bool input_handle(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y)
maybe convert entire function to if/else and make them all customizable keys? */
if (!match) {
if (key == user_settings->key_toggle_peerlist) {
if (self->is_groupchat) {
if (self->type == WINDOW_TYPE_CONFERENCE) {
self->show_peerlist ^= 1;
redraw_groupchat_win(self);
redraw_conference_win(self);
}
match = true;
@ -283,5 +346,9 @@ bool input_handle(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y)
}
}
if (match) {
flag_interface_change(1);
}
return match;
}

View File

@ -24,10 +24,10 @@
#define INPUT_H
/* add a char to input field and buffer for given chatcontext */
void input_new_char(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y);
void input_new_char(ToxWindow *self, wint_t key, int x, int mx_x);
/* Handles non-printable input keys that behave the same for all types of chat windows.
return true if key matches a function, false otherwise */
bool input_handle(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y);
bool input_handle(ToxWindow *self, wint_t key, int x, int mx_x);
#endif /* #define INPUT_H */
#endif /* INPUT_H */

View File

@ -20,19 +20,19 @@
*
*/
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdarg.h>
#include "toxic.h"
#include "windows.h"
#include "conference.h"
#include "line_info.h"
#include "groupchat.h"
#include "settings.h"
#include "notify.h"
#include "message_queue.h"
#include "misc_tools.h"
#include "notify.h"
#include "settings.h"
#include "toxic.h"
#include "windows.h"
extern struct user_settings *user_settings;
@ -40,12 +40,13 @@ void line_info_init(struct history *hst)
{
hst->line_root = calloc(1, sizeof(struct line_info));
if (hst->line_root == NULL)
if (hst->line_root == NULL) {
exit_toxic_err("failed in line_info_init", FATALERR_MEMORY);
}
hst->line_start = hst->line_root;
hst->line_end = hst->line_start;
hst->queue_sz = 0;
hst->queue_size = 0;
}
/* resets line_start (moves to end of chat history) */
@ -53,26 +54,28 @@ void line_info_reset_start(ToxWindow *self, struct history *hst)
{
struct line_info *line = hst->line_end;
if (line->prev == NULL)
if (line == NULL || line->prev == NULL) {
return;
}
int y2, x2;
int y2;
int x2;
getmaxyx(self->window, y2, x2);
UNUSED_VAR(x2);
int side_offst = self->show_peerlist ? SIDEBAR_WIDTH : 0;
int top_offst = self->is_chat || self->is_prompt ? 2 : 0;
int max_y = (y2 - CHATBOX_HEIGHT - top_offst);
int top_offst = (self->type == WINDOW_TYPE_CHAT) || (self->type == WINDOW_TYPE_PROMPT) ? TOP_BAR_HEIGHT : 0;
int max_y = y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT - top_offst;
int curlines = 0;
int nxtlines = line->newlines + (line->len / (x2 - side_offst));
uint16_t curlines = 0;
do {
curlines += 1 + nxtlines;
curlines += line->format_lines;
line = line->prev;
nxtlines = line->newlines + (line->len / (x2 - side_offst));
} while (line->prev && curlines + nxtlines < max_y);
} while (line->prev && curlines + line->format_lines <= max_y);
hst->line_start = line;
self->scroll_pause = false;
}
void line_info_cleanup(struct history *hst)
@ -85,11 +88,10 @@ void line_info_cleanup(struct history *hst)
tmp1 = tmp2;
}
int i;
for (i = 0; i < hst->queue_sz; ++i) {
if (hst->queue[i])
for (size_t i = 0; i < hst->queue_size; ++i) {
if (hst->queue[i]) {
free(hst->queue[i]);
}
}
free(hst);
@ -114,39 +116,250 @@ static void line_info_root_fwd(struct history *hst)
/* returns ptr to queue item 0 and removes it from queue. Returns NULL if queue is empty. */
static struct line_info *line_info_ret_queue(struct history *hst)
{
if (hst->queue_sz <= 0)
if (hst->queue_size == 0) {
return NULL;
}
struct line_info *line = hst->queue[0];
int i;
for (i = 0; i < hst->queue_sz; ++i)
for (size_t i = 0; i < hst->queue_size; ++i) {
hst->queue[i] = hst->queue[i + 1];
}
--hst->queue_sz;
--hst->queue_size;
return line;
}
/* creates new line_info line and puts it in the queue. */
void line_info_add(ToxWindow *self, const char *timestr, const char *name1, const char *name2, uint8_t type,
uint8_t bold, uint8_t colour, const char *msg, ...)
/* Prints a maximum of `n` chars from `s` to `win`.
*
* Return 1 if the string contains a newline byte.
* Return 0 if string does not contain a newline byte.
* Return -1 if printing was aborted.
*/
static int print_n_chars(WINDOW *win, const char *s, size_t n, int max_y)
{
if (!self)
return;
bool newline = false;
char ch;
for (size_t i = 0; i < n && (ch = s[i]); ++i) {
if (ch == '\n') {
newline = true;
int x;
int y;
UNUSED_VAR(x);
getyx(win, y, x);
// make sure cursor will wrap correctly after newline to prevent display bugs
if (y + 1 >= max_y) {
return -1;
}
}
if (win) {
wprintw(win, "%c", ch);
}
}
return newline;
}
/* Returns the index of the last space character in `s` found before `limit`.
* Returns -1 if no space is found.
*/
static int rspace_index(const char *s, int limit)
{
for (int i = limit; i >= 0; --i) {
if (s[i] == ' ') {
return i;
}
}
return -1;
}
/* Returns the first index in `s` containing a newline byte found before `limit`.
* Returns -1 if no newline is found.
*/
static int newline_index(const char *s, int limit)
{
char ch;
for (int i = 0; i < limit && (ch = s[i]); ++i) {
if (ch == '\n') {
return i;
}
}
return -1;
}
/* Returns the number of newline bytes in `s` */
static unsigned int newline_count(const char *s)
{
char ch;
unsigned int count = 0;
for (size_t i = 0; (ch = s[i]); ++i) {
if (ch == '\n') {
++count;
}
}
return count;
}
/* Prints `line` message to window, wrapping at the last word that fits on the current line.
* This function updates the `format_lines` field of `line` according to current window dimensions.
*
* If `win` is null nothing will be printed to the window. This is useful to set the
* `format_lines` field on initialization.
*
* Return 0 on success.
* Return -1 if not all characters in line's message were printed to screen.
*/
static int print_wrap(WINDOW *win, struct line_info *line, int max_x, int max_y)
{
int x;
int y;
UNUSED_VAR(y);
const char *msg = line->msg;
uint16_t length = line->msg_len;
uint16_t lines = 0;
const int x_start = line->len - line->msg_len - 1; // manually keep track of x position because ncurses sucks
int x_limit = max_x - x_start;
if (x_limit <= 1) {
fprintf(stderr, "Warning: x_limit <= 0 in print_wrap(): %d\n", x_limit);
return -1;
}
while (msg) {
getyx(win, y, x);
// next line would print past window limit so we abort; we don't want to update format_lines
if (x > x_start) {
return -1;
}
if (length < x_limit) {
int p_ret = print_n_chars(win, msg, length, max_y);
if (p_ret == 1) {
lines += newline_count(msg);
} else if (p_ret == -1) {
return -1;
}
++lines;
break;
}
int newline_idx = newline_index(msg, x_limit - 1);
if (newline_idx >= 0) {
if (print_n_chars(win, msg, newline_idx + 1, max_y) == -1) {
return -1;
}
msg += (newline_idx + 1);
length -= (newline_idx + 1);
x_limit = max_x; // if we find a newline we stop adding column padding for rest of message
++lines;
continue;
}
int space_idx = rspace_index(msg, x_limit - 1);
if (space_idx >= 1) {
if (print_n_chars(win, msg, space_idx, max_y) == -1) {
return -1;
}
msg += space_idx + 1;
length -= (space_idx + 1);
if (win) {
waddch(win, '\n');
}
} else {
if (print_n_chars(win, msg, x_limit, max_y) == -1) {
return -1;
}
msg += x_limit;
length -= x_limit;
}
// Add padding to the start of the next line
if (win && x_limit < max_x) {
for (size_t i = 0; i < x_start; ++i) {
waddch(win, ' ');
}
}
++lines;
}
if (win && line->noread_flag) {
getyx(win, y, x);
if (x >= max_x - 1 || x == x_start) {
++lines;
}
wattron(win, COLOR_PAIR(RED));
wprintw(win, " x");
wattroff(win, COLOR_PAIR(RED));
}
line->format_lines = lines;
return 0;
}
static void line_info_init_line(ToxWindow *self, struct line_info *line)
{
int y2;
int x2;
UNUSED_VAR(y2);
getmaxyx(self->window, y2, x2);
const int max_y = y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT;
const int max_x = self->show_peerlist ? x2 - 1 - SIDEBAR_WIDTH : x2;
print_wrap(NULL, line, max_x, max_y);
}
/* creates new line_info line and puts it in the queue.
*
* Returns the id of the new line.
* Returns -1 on failure.
*/
int line_info_add(ToxWindow *self, bool show_timestamp, const char *name1, const char *name2, LINE_TYPE type,
uint8_t bold, uint8_t colour, const char *msg, ...)
{
if (!self) {
return -1;
}
struct history *hst = self->chatwin->hst;
if (hst->queue_sz >= MAX_LINE_INFO_QUEUE)
return;
if (hst->queue_size >= MAX_LINE_INFO_QUEUE) {
return -1;
}
struct line_info *new_line = calloc(1, sizeof(struct line_info));
if (new_line == NULL)
if (new_line == NULL) {
exit_toxic_err("failed in line_info_add", FATALERR_MEMORY);
}
char frmt_msg[MAX_LINE_INFO_MSG_SIZE] = {0};
char frmt_msg[MAX_LINE_INFO_MSG_SIZE];
frmt_msg[0] = 0;
va_list args;
va_start(args, msg);
@ -161,33 +374,33 @@ void line_info_add(ToxWindow *self, const char *timestr, const char *name1, cons
/* fallthrough */
case OUT_ACTION:
len += strlen(user_settings->line_normal) + 2;
len += strlen(user_settings->line_normal) + 2; // two spaces
break;
case IN_MSG:
/* fallthrough */
case OUT_MSG:
len += strlen(user_settings->line_normal) + 3;
len += strlen(user_settings->line_normal) + 3; // two spaces and a ':' char
break;
case CONNECTION:
len += strlen(user_settings->line_join) + 2;
len += strlen(user_settings->line_join) + 2; // two spaces
break;
case DISCONNECTION:
len += strlen(user_settings->line_quit) + 2;
len += strlen(user_settings->line_quit) + 2; // two spaces
break;
case SYS_MSG:
break;
case NAME_CHANGE:
len += strlen(user_settings->line_alert) + 1;
len += strlen(user_settings->line_alert) + 2; // two spaces
break;
case PROMPT:
++len;
len += 2; // '$' char and a space
break;
default:
@ -195,20 +408,16 @@ void line_info_add(ToxWindow *self, const char *timestr, const char *name1, cons
break;
}
uint16_t msg_len = 0;
if (frmt_msg[0]) {
snprintf(new_line->msg, sizeof(new_line->msg), "%s", frmt_msg);
len += strlen(new_line->msg);
int i;
for (i = 0; frmt_msg[i]; ++i) {
if (frmt_msg[i] == '\n')
++new_line->newlines;
}
msg_len = strlen(new_line->msg);
len += msg_len;
}
if (timestr) {
snprintf(new_line->timestr, sizeof(new_line->timestr), "%s", timestr);
if (show_timestamp) {
get_time_str(new_line->timestr, sizeof(new_line->timestr));
len += strlen(new_line->timestr) + 1;
}
@ -222,14 +431,22 @@ void line_info_add(ToxWindow *self, const char *timestr, const char *name1, cons
len += strlen(new_line->name2);
}
new_line->id = (hst->line_end->id + 1 + hst->queue_size) % INT_MAX;
new_line->len = len;
new_line->msg_len = msg_len;
new_line->type = type;
new_line->bold = bold;
new_line->colour = colour;
new_line->noread_flag = false;
new_line->timestamp = get_unix_time();
hst->queue[hst->queue_sz++] = new_line;
line_info_init_line(self, new_line);
hst->queue[hst->queue_size++] = new_line;
flag_interface_change(1);
return new_line->id;
}
/* adds a single queue item to hst if possible. only called once per call to line_info_print() */
@ -238,37 +455,25 @@ static void line_info_check_queue(ToxWindow *self)
struct history *hst = self->chatwin->hst;
struct line_info *line = line_info_ret_queue(hst);
if (line == NULL)
if (line == NULL) {
flag_interface_change(0);
return;
}
if (hst->start_id > user_settings->history_size)
if (hst->start_id > user_settings->history_size) {
line_info_root_fwd(hst);
}
line->id = hst->line_end->id + 1;
line->prev = hst->line_end;
hst->line_end->next = line;
hst->line_end = line;
hst->line_end->id = line->id;
int y, y2, x, x2;
getmaxyx(self->window, y2, x2);
getyx(self->chatwin->history, y, x);
(void) x;
if (x2 <= SIDEBAR_WIDTH)
return;
int offst = self->show_peerlist ? SIDEBAR_WIDTH : 0; /* offset width of groupchat sidebar */
int lines = 1 + line->newlines + (line->len / (x2 - offst));
int max_y = y2 - CHATBOX_HEIGHT;
/* move line_start forward proportionate to the number of new lines */
if (y + lines - 1 >= max_y) {
while (lines > 0 && hst->line_start->next) {
lines -= 1 + hst->line_start->next->newlines + (hst->line_start->next->len / (x2 - offst));
hst->line_start = hst->line_start->next;
++hst->start_id;
}
if (!self->scroll_pause) {
line_info_reset_start(self, hst);
}
flag_interface_change(1);
}
#define NOREAD_FLAG_TIMEOUT 5 /* seconds before a sent message with no read receipt is flagged as unread */
@ -277,8 +482,9 @@ void line_info_print(ToxWindow *self)
{
ChatContext *ctx = self->chatwin;
if (ctx == NULL)
if (ctx == NULL) {
return;
}
struct history *hst = ctx->hst;
@ -286,22 +492,47 @@ void line_info_print(ToxWindow *self)
line_info_check_queue(self);
WINDOW *win = ctx->history;
wclear(win);
int y2, x2;
int y2;
int x2;
getmaxyx(self->window, y2, x2);
if (x2 <= SIDEBAR_WIDTH)
if (x2 - 1 <= SIDEBAR_WIDTH) { // leave room on x axis for sidebar padding
return;
}
if (self->is_groupchat)
if (self->type == WINDOW_TYPE_CONFERENCE) {
wmove(win, 0, 0);
else
wmove(win, 2, 0);
} else {
wmove(win, TOP_BAR_HEIGHT, 0);
}
struct line_info *line = hst->line_start->next;
int numlines = 0;
while (line && numlines++ <= y2) {
if (!line) {
return;
}
const int max_y = y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT;
const int max_x = self->show_peerlist ? x2 - 1 - SIDEBAR_WIDTH : x2;
uint16_t numlines = line->format_lines;
int print_ret = 0;
while (line && numlines++ <= max_y && print_ret == 0) {
int y;
int x;
UNUSED_VAR(y);
getyx(win, y, x);
if (x > 0) { // Prevents us from printing off the screen
break;
}
uint8_t type = line->type;
switch (type) {
@ -318,49 +549,42 @@ void line_info_print(ToxWindow *self)
int nameclr = GREEN;
if (line->colour)
if (line->colour) {
nameclr = line->colour;
else if (type == IN_MSG)
} else if (type == IN_MSG) {
nameclr = CYAN;
}
wattron(win, COLOR_PAIR(nameclr));
wprintw(win, "%s %s: ", user_settings->line_normal, line->name1);
wattroff(win, COLOR_PAIR(nameclr));
char *msg = line->msg;
while (msg) {
char *line = strsep(&msg, "\n");
if (line[0] == '>')
wattron(win, COLOR_PAIR(GREEN));
else if (line[0] == '<')
wattron(win, COLOR_PAIR(RED));
wprintw(win, "%s%c", line, msg ? '\n' : '\0');
if (line[0] == '>')
wattroff(win, COLOR_PAIR(GREEN));
else if (line[0] == '<')
wattroff(win, COLOR_PAIR(RED));
// change the \0 set by strsep back to \n
if (msg)
msg[-1] = '\n';
if (line->msg[0] == 0) {
waddch(win, '\n');
break;
}
if (type == OUT_MSG && timed_out(line->timestamp, NOREAD_FLAG_TIMEOUT)) {
if (line->msg[0] == '>') {
wattron(win, COLOR_PAIR(GREEN));
} else if (line->msg[0] == '<') {
wattron(win, COLOR_PAIR(RED));
wprintw(win, " x", line->msg);
wattroff(win, COLOR_PAIR(RED));
}
if (line->noread_flag == false) {
print_ret = print_wrap(win, line, max_x, max_y);
if (line->msg[0] == '>') {
wattroff(win, COLOR_PAIR(GREEN));
} else if (line->msg[0] == '<') {
wattroff(win, COLOR_PAIR(RED));
}
if (type == OUT_MSG && !line->read_flag) {
if (timed_out(line->timestamp, NOREAD_FLAG_TIMEOUT)) {
line->noread_flag = true;
line->len += 2;
}
}
wprintw(win, "\n", line->msg);
waddch(win, '\n');
break;
case OUT_ACTION_READ:
@ -375,21 +599,17 @@ void line_info_print(ToxWindow *self)
wattroff(win, COLOR_PAIR(BLUE));
wattron(win, COLOR_PAIR(YELLOW));
wprintw(win, "%s %s %s", user_settings->line_normal, line->name1, line->msg);
wprintw(win, "%s %s ", user_settings->line_normal, line->name1);
print_ret = print_wrap(win, line, max_x, max_y);
wattroff(win, COLOR_PAIR(YELLOW));
if (type == OUT_ACTION && timed_out(line->timestamp, NOREAD_FLAG_TIMEOUT)) {
wattron(win, COLOR_PAIR(RED));
wprintw(win, " x", line->msg);
wattroff(win, COLOR_PAIR(RED));
if (line->noread_flag == false) {
if (type == OUT_ACTION && !line->read_flag) {
if (timed_out(line->timestamp, NOREAD_FLAG_TIMEOUT)) {
line->noread_flag = true;
line->len += 2;
}
}
wprintw(win, "\n", line->msg);
waddch(win, '\n');
break;
case SYS_MSG:
@ -399,19 +619,24 @@ void line_info_print(ToxWindow *self)
wattroff(win, COLOR_PAIR(BLUE));
}
if (line->bold)
if (line->bold) {
wattron(win, A_BOLD);
}
if (line->colour)
if (line->colour) {
wattron(win, COLOR_PAIR(line->colour));
}
wprintw(win, "%s\n", line->msg);
print_ret = print_wrap(win, line, max_x, max_y);
waddch(win, '\n');
if (line->bold)
if (line->bold) {
wattroff(win, A_BOLD);
}
if (line->colour)
if (line->colour) {
wattroff(win, COLOR_PAIR(line->colour));
}
break;
@ -420,10 +645,11 @@ void line_info_print(ToxWindow *self)
wprintw(win, "$ ");
wattroff(win, COLOR_PAIR(GREEN));
if (line->msg[0])
wprintw(win, "%s", line->msg);
if (line->msg[0]) {
print_ret = print_wrap(win, line, max_x, max_y);
}
wprintw(win, "\n");
waddch(win, '\n');
break;
case CONNECTION:
@ -438,7 +664,9 @@ void line_info_print(ToxWindow *self)
wprintw(win, "%s ", line->name1);
wattroff(win, A_BOLD);
wprintw(win, "%s\n", line->msg);
print_ret = print_wrap(win, line, max_x, max_y);
waddch(win, '\n');
wattroff(win, COLOR_PAIR(line->colour));
break;
@ -455,7 +683,9 @@ void line_info_print(ToxWindow *self)
wprintw(win, "%s ", line->name1);
wattroff(win, A_BOLD);
wprintw(win, "%s\n", line->msg);
print_ret = print_wrap(win, line, max_x, max_y);
waddch(win, '\n');
wattroff(win, COLOR_PAIR(line->colour));
break;
@ -471,7 +701,7 @@ void line_info_print(ToxWindow *self)
wprintw(win, "%s", line->name1);
wattroff(win, A_BOLD);
wprintw(win, "%s", line->msg);
print_ret = print_wrap(win, line, max_x, max_y);
wattron(win, A_BOLD);
wprintw(win, "%s\n", line->name2);
@ -485,8 +715,41 @@ void line_info_print(ToxWindow *self)
}
/* keep calling until queue is empty */
if (hst->queue_sz > 0)
if (hst->queue_size > 0) {
line_info_print(self);
}
}
/*
* Return true if all lines starting from `line` can fit on the screen.
*/
static bool line_info_screen_fit(ToxWindow *self, struct line_info *line)
{
if (!line) {
return true;
}
int x2;
int y2;
getmaxyx(self->chatwin->history, y2, x2);
UNUSED_VAR(x2);
const int top_offset = (self->type == WINDOW_TYPE_CHAT) || (self->type == WINDOW_TYPE_PROMPT) ? TOP_BAR_HEIGHT : 0;
const int max_y = y2 - top_offset;
uint16_t lines = line->format_lines;
while (line) {
if (lines > max_y) {
return false;
}
lines += line->format_lines;
line = line->next;
}
return true;
}
/* puts msg in specified line_info msg buffer */
@ -496,55 +759,96 @@ void line_info_set(ToxWindow *self, uint32_t id, char *msg)
while (line) {
if (line->id == id) {
size_t new_len = strlen(msg);
line->len = line->len - line->msg_len + new_len;
line->msg_len = new_len;
snprintf(line->msg, sizeof(line->msg), "%s", msg);
return;
}
line = line->prev;
}
flag_interface_change(1);
}
/* static void line_info_goto_root(struct history *hst)
static void line_info_scroll_up(ToxWindow *self, struct history *hst)
{
hst->line_start = hst->line_root;
} */
static void line_info_scroll_up(struct history *hst)
{
if (hst->line_start->prev)
if (hst->line_start->prev) {
hst->line_start = hst->line_start->prev;
else sound_notify(NULL, notif_error, NT_ALWAYS, NULL);
self->scroll_pause = true;
}
flag_interface_change(1);
}
static void line_info_scroll_down(struct history *hst)
static void line_info_scroll_down(ToxWindow *self, struct history *hst)
{
if (hst->line_start->next)
hst->line_start = hst->line_start->next;
else sound_notify(NULL, notif_error, NT_ALWAYS, NULL);
struct line_info *next = hst->line_start->next;
if (next && self->scroll_pause) {
if (line_info_screen_fit(self, next->next)) {
line_info_reset_start(self, hst);
} else {
hst->line_start = next;
}
} else {
line_info_reset_start(self, hst);
}
flag_interface_change(1);
}
static void line_info_page_up(ToxWindow *self, struct history *hst)
{
int x2, y2;
int x2;
int y2;
getmaxyx(self->window, y2, x2);
(void) x2;
int jump_dist = y2 / 2;
int i;
for (i = 0; i < jump_dist && hst->line_start->prev; ++i)
UNUSED_VAR(x2);
const int top_offset = (self->type == WINDOW_TYPE_CHAT) || (self->type == WINDOW_TYPE_PROMPT) ? TOP_BAR_HEIGHT : 0;
const int max_y = y2 - top_offset;
size_t jump_dist = max_y / 2;
for (size_t i = 0; i < jump_dist && hst->line_start->prev; ++i) {
hst->line_start = hst->line_start->prev;
}
self->scroll_pause = true;
flag_interface_change(1);
}
static void line_info_page_down(ToxWindow *self, struct history *hst)
{
int x2, y2;
getmaxyx(self->window, y2, x2);
(void) x2;
int jump_dist = y2 / 2;
int i;
if (!self->scroll_pause) {
return;
}
for (i = 0; i < jump_dist && hst->line_start->next; ++i)
hst->line_start = hst->line_start->next;
int x2;
int y2;
getmaxyx(self->chatwin->history, y2, x2);
UNUSED_VAR(x2);
const int top_offset = (self->type == WINDOW_TYPE_CHAT) || (self->type == WINDOW_TYPE_PROMPT) ? TOP_BAR_HEIGHT : 0;
const int max_y = y2 - top_offset;
size_t jump_dist = max_y / 2;
struct line_info *next = hst->line_start->next;
for (size_t i = 0; i < jump_dist && next; ++i) {
if (line_info_screen_fit(self, next->next)) {
line_info_reset_start(self, hst);
break;
}
hst->line_start = next;
next = hst->line_start->next;
}
flag_interface_change(1);
}
bool line_info_onKey(ToxWindow *self, wint_t key)
@ -557,15 +861,19 @@ bool line_info_onKey(ToxWindow *self, wint_t key)
} else if (key == user_settings->key_half_page_down) {
line_info_page_down(self, hst);
} else if (key == user_settings->key_scroll_line_up) {
line_info_scroll_up(hst);
line_info_scroll_up(self, hst);
} else if (key == user_settings->key_scroll_line_down) {
line_info_scroll_down(hst);
line_info_scroll_down(self, hst);
} else if (key == user_settings->key_page_bottom) {
line_info_reset_start(self, hst);
} else {
match = false;
}
if (match) {
flag_interface_change(1);
}
return match;
}
@ -573,4 +881,6 @@ void line_info_clear(struct history *hst)
{
hst->line_start = hst->line_end;
hst->start_id = hst->line_start->id;
flag_interface_change(1);
}

View File

@ -23,15 +23,15 @@
#ifndef LINE_INFO_H
#define LINE_INFO_H
#include "windows.h"
#include "toxic.h"
#include "windows.h"
#define MAX_HISTORY 100000
#define MIN_HISTORY 40
#define MAX_LINE_INFO_QUEUE 1024
#define MAX_LINE_INFO_MSG_SIZE MAX_STR_SIZE + TOXIC_MAX_NAME_LENGTH + 32 /* needs extra room for log loading */
#define MAX_LINE_INFO_MSG_SIZE (MAX_STR_SIZE + TOXIC_MAX_NAME_LENGTH + 32) /* needs extra room for log loading */
enum {
typedef enum LINE_TYPE {
SYS_MSG,
IN_MSG,
OUT_MSG,
@ -54,10 +54,12 @@ struct line_info {
uint8_t type;
uint8_t bold;
uint8_t colour;
uint8_t noread_flag; /* true if a line should be flagged as unread */
bool noread_flag; /* true if a line should be flagged as unread */
bool read_flag; /* true if a message has been flagged as read */
uint32_t id;
uint16_t len; /* combined len of entire line */
uint8_t newlines;
uint16_t len; /* combined length of entire line */
uint16_t msg_len; /* length of the message */
uint16_t format_lines; /* number of lines the combined string takes up (dynamically set) */
struct line_info *prev;
struct line_info *next;
@ -71,12 +73,16 @@ struct history {
uint32_t start_id; /* keeps track of where line_start should be when at bottom of history */
struct line_info *queue[MAX_LINE_INFO_QUEUE];
int queue_sz;
size_t queue_size;
};
/* creates new line_info line and puts it in the queue. */
void line_info_add(ToxWindow *self, const char *timestr, const char *name1, const char *name2, uint8_t type,
uint8_t bold, uint8_t colour, const char *msg, ...);
/* creates new line_info line and puts it in the queue.
*
* Returns the id of the new line.
* Returns -1 on failure.
*/
int line_info_add(ToxWindow *self, bool show_timestamp, const char *name1, const char *name2, LINE_TYPE type,
uint8_t bold, uint8_t colour, const char *msg, ...);
/* Prints a section of history starting at line_start */
void line_info_print(ToxWindow *self);
@ -96,4 +102,4 @@ void line_info_reset_start(ToxWindow *self, struct history *hst);
void line_info_init(struct history *hst);
bool line_info_onKey(ToxWindow *self, wint_t key); /* returns true if key is a match */
#endif /* #define LINE_INFO_H */
#endif /* LINE_INFO_H */

314
src/log.c
View File

@ -22,41 +22,46 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <time.h>
#include "configdir.h"
#include "line_info.h"
#include "log.h"
#include "misc_tools.h"
#include "settings.h"
#include "toxic.h"
#include "windows.h"
#include "misc_tools.h"
#include "log.h"
#include "settings.h"
#include "line_info.h"
extern struct user_settings *user_settings;
/* There are three types of logs: chat logs, groupchat logs, and prompt logs (see LOG_TYPE in log.h)
A prompt log is in the format: LOGDIR/selfkey-home.log
A chat log is in the format: LOGDIR/selfkey-friendname-otherkey.log
A groupchat log is in the format: LOGDIR/selfkey-groupname-date[time].log
Only the first (KEY_IDENT_DIGITS * 2) numbers of the key are used.
Returns 0 on success, -1 if the path is too long */
static int get_log_path(char *dest, int destsize, char *name, const char *selfkey, const char *otherkey, int logtype)
/* Creates a log path and puts it in `dest.
*
* There are two types of logs: chat logs and prompt logs (see LOG_TYPE in log.h)
* A prompt log is in the format: LOGDIR/selfkey-home.log
* A chat log is in the format: LOGDIR/selfkey-name-otherkey.log
*
* For friend chats `otherkey` is the first 6 bytes of the friend's Tox ID.
* For Conferences/groups `otherkey` is the first 6 bytes of the group's unique ID.
*
* Return path length on success.
* Return -1 if the path is too long.
*/
static int get_log_path(char *dest, int destsize, const char *name, const char *selfkey, const char *otherkey)
{
if (!valid_nick(name))
if (!valid_nick(name)) {
name = UNKNOWN_NAME;
}
const char *namedash = logtype == LOG_PROMPT ? "" : "-";
const char *namedash = otherkey ? "-" : "";
const char *set_path = user_settings->chatlogs_path;
char *user_config_dir = get_user_config_dir();
int path_len = strlen(name) + strlen(".log") + strlen("-") + strlen(namedash);
path_len += strlen(set_path) ? *set_path : strlen(user_config_dir) + strlen(LOGDIR);
/* first 6 digits of selfkey */
char self_id[32];
/* first 6 bytes of selfkey */
char self_id[32] = {0};
path_len += KEY_IDENT_DIGITS * 2;
sprintf(&self_id[0], "%02X", selfkey[0] & 0xff);
sprintf(&self_id[2], "%02X", selfkey[1] & 0xff);
@ -65,19 +70,13 @@ static int get_log_path(char *dest, int destsize, char *name, const char *selfke
char other_id[32] = {0};
switch (logtype) {
case LOG_CHAT:
path_len += KEY_IDENT_DIGITS * 2;
sprintf(&other_id[0], "%02X", otherkey[0] & 0xff);
sprintf(&other_id[2], "%02X", otherkey[1] & 0xff);
sprintf(&other_id[4], "%02X", otherkey[2] & 0xff);
other_id[KEY_IDENT_DIGITS * 2] = '\0';
break;
case LOG_GROUP:
strftime(other_id, sizeof(other_id), "%Y-%m-%d[%H:%M:%S]", get_time());
path_len += strlen(other_id);
break;
if (otherkey) {
/* first 6 bytes of otherkey */
path_len += KEY_IDENT_DIGITS * 2;
sprintf(&other_id[0], "%02X", otherkey[0] & 0xff);
sprintf(&other_id[2], "%02X", otherkey[1] & 0xff);
sprintf(&other_id[4], "%02X", otherkey[2] & 0xff);
other_id[KEY_IDENT_DIGITS * 2] = '\0';
}
if (path_len >= destsize) {
@ -85,32 +84,43 @@ static int get_log_path(char *dest, int destsize, char *name, const char *selfke
return -1;
}
if (!string_is_empty(set_path))
if (!string_is_empty(set_path)) {
snprintf(dest, destsize, "%s%s-%s%s%s.log", set_path, self_id, name, namedash, other_id);
else
} else {
snprintf(dest, destsize, "%s%s%s-%s%s%s.log", user_config_dir, LOGDIR, self_id, name, namedash, other_id);
}
free(user_config_dir);
return 0;
return path_len;
}
/* Opens log file or creates a new one */
static int init_logging_session(char *name, const char *selfkey, const char *otherkey, struct chatlog *log, int logtype)
/* Initializes log path for `log`.
*
* Return 0 on success.
* Return -1 on failure.
*/
static int init_logging_session(const char *name, const char *selfkey, const char *otherkey, struct chatlog *log,
LOG_TYPE type)
{
if (selfkey == NULL || (logtype == LOG_CHAT && otherkey == NULL))
if (log == NULL) {
return -1;
}
if (selfkey == NULL || (type == LOG_TYPE_CHAT && otherkey == NULL)) {
return -1;
}
char log_path[MAX_STR_SIZE];
if (get_log_path(log_path, sizeof(log_path), name, selfkey, otherkey, logtype) == -1)
return -1;
int path_len = get_log_path(log_path, sizeof(log_path), name, selfkey, otherkey);
log->file = fopen(log_path, "a+");
snprintf(log->path, sizeof(log->path), "%s", log_path);
if (log->file == NULL)
if (path_len == -1 || path_len >= sizeof(log->path)) {
return -1;
}
memcpy(log->path, log_path, path_len);
log->path[path_len] = 0;
return 0;
}
@ -119,8 +129,13 @@ static int init_logging_session(char *name, const char *selfkey, const char *oth
void write_to_log(const char *msg, const char *name, struct chatlog *log, bool event)
{
if (!log->log_on)
if (log == NULL) {
return;
}
if (!log->log_on) {
return;
}
if (log->file == NULL) {
log->log_on = false;
@ -129,10 +144,11 @@ void write_to_log(const char *msg, const char *name, struct chatlog *log, bool e
char name_frmt[TOXIC_MAX_NAME_LENGTH + 3];
if (event)
if (event) {
snprintf(name_frmt, sizeof(name_frmt), "* %s", name);
else
} else {
snprintf(name_frmt, sizeof(name_frmt), "%s:", name);
}
const char *t = user_settings->log_timestamp_format;
char s[MAX_STR_SIZE];
@ -147,86 +163,162 @@ void write_to_log(const char *msg, const char *name, struct chatlog *log, bool e
void log_disable(struct chatlog *log)
{
if (log->file != NULL)
fclose(log->file);
if (log == NULL) {
return;
}
memset(log, 0, sizeof(struct chatlog));
if (log->file != NULL) {
fclose(log->file);
log->file = NULL;
}
log->lastwrite = 0;
log->log_on = false;
}
int log_enable(char *name, const char *selfkey, const char *otherkey, struct chatlog *log, int logtype)
int log_enable(struct chatlog *log)
{
log->log_on = true;
if (log->file != NULL)
return 0;
if (init_logging_session(name, selfkey, otherkey, log, logtype) == -1) {
log_disable(log);
if (log == NULL) {
return -1;
}
if (log->log_on) {
return 0;
}
if (*log->path == 0) {
return -1;
}
if (log->file != NULL) {
return -1;
}
log->file = fopen(log->path, "a+");
if (log->file == NULL) {
return -1;
}
log->log_on = true;
return 0;
}
/* Loads previous history from chat log */
void load_chat_history(ToxWindow *self, struct chatlog *log)
/* Initializes a log. This function must be called before any other logging operations.
*
* Return 0 on success.
* Return -1 on failure.
*/
int log_init(struct chatlog *log, const char *name, const char *selfkey, const char *otherkey, LOG_TYPE type)
{
if (log->file == NULL)
return;
if (log == NULL) {
return -1;
}
if (log->file != NULL || log->log_on) {
fprintf(stderr, "Warning: Called log_init() on an already initialized log\n");
return -1;
}
if (init_logging_session(name, selfkey, otherkey, log, type) == -1) {
return -1;
}
log_disable(log);
return 0;
}
/* Loads chat log history and prints it to `self` window.
*
* Return 0 on success or if log file doesn't exist.
* Return -1 on failure.
*/
int load_chat_history(ToxWindow *self, struct chatlog *log)
{
if (log == NULL) {
return -1;
}
if (*log->path == 0) {
return -1;
}
off_t sz = file_size(log->path);
if (sz <= 0)
return;
char *hstbuf = malloc(sz + 1);
if (hstbuf == NULL)
exit_toxic_err("failed in load_chat_history", FATALERR_MEMORY);
if (fseek(log->file, 0L, SEEK_SET) == -1) {
free(hstbuf);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, " * Failed to read log file");
return;
if (sz <= 0) {
return 0;
}
if (fread(hstbuf, sz, 1, log->file) != 1) {
free(hstbuf);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, " * Failed to read log file");
return;
FILE *fp = fopen(log->path, "r");
if (fp == NULL) {
return -1;
}
hstbuf[sz] = '\0';
char *buf = malloc(sz + 1);
if (buf == NULL) {
fclose(fp);
return -1;
}
if (fseek(fp, 0L, SEEK_SET) == -1) {
free(buf);
fclose(fp);
return -1;
}
if (fread(buf, sz, 1, fp) != 1) {
free(buf);
fclose(fp);
return -1;
}
fclose(fp);
buf[sz] = 0;
/* Number of history lines to load: must not be larger than MAX_LINE_INFO_QUEUE - 2 */
int L = MIN(MAX_LINE_INFO_QUEUE - 2, user_settings->history_size);
int start, count = 0;
int start = 0;
int count = 0;
/* start at end and backtrace L lines or to the beginning of buffer */
for (start = sz - 1; start >= 0 && count < L; --start) {
if (hstbuf[start] == '\n')
if (buf[start] == '\n') {
++count;
}
}
const char *line = strtok(&hstbuf[start + 1], "\n");
char *tmp = NULL;
const char *line = strtok_r(&buf[start + 1], "\n", &tmp);
if (line == NULL) {
free(hstbuf);
return;
free(buf);
return -1;
}
while (line != NULL && count--) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", line);
line = strtok(NULL, "\n");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", line);
line = strtok_r(NULL, "\n", &tmp);
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "");
free(hstbuf);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, YELLOW, "---");
free(buf);
return 0;
}
/* renames chatlog file replacing src with dest.
Returns 0 on success or if no log exists, -1 on failure. */
int rename_logfile(char *src, char *dest, const char *selfkey, const char *otherkey, int winnum)
/* Renames chatlog file `src` to `dest`.
*
* Return 0 on success or if no log exists.
* Return -1 on failure.
*/
int rename_logfile(const char *src, const char *dest, const char *selfkey, const char *otherkey, int winnum)
{
ToxWindow *toxwin = get_window_ptr(winnum);
struct chatlog *log = NULL;
@ -235,36 +327,60 @@ int rename_logfile(char *src, char *dest, const char *selfkey, const char *other
/* disable log if necessary and save its state */
if (toxwin != NULL) {
log = toxwin->chatwin->log;
if (log == NULL) {
return -1;
}
log_on = log->log_on;
}
if (log_on)
if (log_on) {
log_disable(log);
}
char newpath[MAX_STR_SIZE];
char oldpath[MAX_STR_SIZE];
if (get_log_path(oldpath, sizeof(oldpath), src, selfkey, otherkey, LOG_CHAT) == -1)
if (get_log_path(oldpath, sizeof(oldpath), src, selfkey, otherkey) == -1) {
goto on_error;
}
if (!file_exists(oldpath))
if (!file_exists(oldpath)) {
init_logging_session(dest, selfkey, otherkey, log, LOG_TYPE_CHAT); // still need to rename path
return 0;
}
if (get_log_path(newpath, sizeof(newpath), dest, selfkey, otherkey, LOG_CHAT) == -1)
int new_path_len = get_log_path(newpath, sizeof(newpath), dest, selfkey, otherkey);
if (new_path_len == -1 || new_path_len >= MAX_STR_SIZE) {
goto on_error;
}
if (rename(oldpath, newpath) != 0)
if (file_exists(newpath)) {
if (remove(oldpath) != 0) {
fprintf(stderr, "Warning: remove() failed to remove log path `%s`\n", oldpath);
}
} else if (rename(oldpath, newpath) != 0) {
goto on_error;
}
if (log_on)
log_enable(dest, selfkey, otherkey, log, LOG_CHAT);
if (log != NULL) {
memcpy(log->path, newpath, new_path_len);
log->path[new_path_len] = 0;
if (log_on) {
log_enable(log);
}
}
return 0;
on_error:
if (log_on)
log_enable(src, selfkey, otherkey, log, LOG_CHAT);
if (log_on) {
log_enable(log);
}
return -1;
}

View File

@ -30,30 +30,43 @@ struct chatlog {
bool log_on; /* specific to current chat window */
};
enum {
LOG_GROUP,
LOG_PROMPT,
LOG_CHAT,
typedef enum LOG_TYPE {
LOG_TYPE_PROMPT,
LOG_TYPE_CHAT,
} LOG_TYPE;
/* Initializes a log. This function must be called before any other logging operations.
*
* Return 0 on success.
* Return -1 on failure.
*/
int log_init(struct chatlog *log, const char *name, const char *selfkey, const char *otherkey, LOG_TYPE type);
/* formats/writes line to log file */
void write_to_log(const char *msg, const char *name, struct chatlog *log, bool event);
/* enables logging for specified log and creates/fetches file if necessary.
/* enables logging for specified log.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int log_enable(char *name, const char *selfkey, const char *otherkey, struct chatlog *log, int logtype);
int log_enable(struct chatlog *log);
/* disables logging for specified log and closes file */
void log_disable(struct chatlog *log);
/* Loads previous history from chat log */
void load_chat_history(ToxWindow *self, struct chatlog *log);
/* Loads chat log history and prints it to `self` window.
*
* Return 0 on success or if log file doesn't exist.
* Return -1 on failure.
*/
int load_chat_history(ToxWindow *self, struct chatlog *log);
/* renames chatlog file replacing src with dest.
Returns 0 on success or if no log exists, -1 on failure. */
int rename_logfile(char *src, char *dest, const char *selfkey, const char *otherkey, int winnum);
/* Renames chatlog file `src` to `dest`.
*
* Return 0 on success or if no log exists.
* Return -1 on failure.
*/
int rename_logfile(const char *src, const char *dest, const char *selfkey, const char *otherkey, int winnum);
#endif /* #define LOG_H */
#endif /* LOG_H */

View File

@ -22,12 +22,12 @@
#include <stdlib.h>
#include "toxic.h"
#include "windows.h"
#include "message_queue.h"
#include "misc_tools.h"
#include "line_info.h"
#include "log.h"
#include "message_queue.h"
#include "misc_tools.h"
#include "toxic.h"
#include "windows.h"
void cqueue_cleanup(struct chat_queue *q)
{
@ -42,19 +42,24 @@ void cqueue_cleanup(struct chat_queue *q)
free(q);
}
void cqueue_add(struct chat_queue *q, const char *msg, size_t len, uint8_t type, uint32_t line_id)
void cqueue_add(struct chat_queue *q, const char *msg, size_t len, uint8_t type, int line_id)
{
if (line_id < 0) {
return;
}
struct cqueue_msg *new_m = malloc(sizeof(struct cqueue_msg));
if (new_m == NULL)
if (new_m == NULL) {
exit_toxic_err("failed in cqueue_message", FATALERR_MEMORY);
}
snprintf(new_m->message, sizeof(new_m->message), "%s", msg);
new_m->len = len;
new_m->type = type;
new_m->line_id = line_id;
new_m->last_send_try = 0;
new_m->receipt = 0;
new_m->receipt = -1;
new_m->next = NULL;
if (q->root == NULL) {
@ -81,9 +86,9 @@ static void cqueue_mark_read(ToxWindow *self, struct cqueue_msg *msg)
line->type = msg->type == OUT_ACTION ? OUT_ACTION_READ : OUT_MSG_READ;
if (line->noread_flag == true) {
line->len -= 2;
if (line->noread_flag) {
line->noread_flag = false;
line->read_flag = true;
}
return;
@ -93,6 +98,7 @@ static void cqueue_mark_read(ToxWindow *self, struct cqueue_msg *msg)
/* removes message with matching receipt from queue, writes to log and updates line to show the message was received. */
void cqueue_remove(ToxWindow *self, Tox *m, uint32_t receipt)
{
struct chatlog *log = self->chatwin->log;
struct chat_queue *q = self->chatwin->cqueue;
struct cqueue_msg *msg = q->root;
@ -102,53 +108,84 @@ void cqueue_remove(ToxWindow *self, Tox *m, uint32_t receipt)
continue;
}
char selfname[TOX_MAX_NAME_LENGTH];
tox_self_get_name(m, (uint8_t *) selfname);
if (log->log_on) {
char selfname[TOX_MAX_NAME_LENGTH];
tox_self_get_name(m, (uint8_t *) selfname);
size_t len = tox_self_get_name_size(m);
selfname[len] = '\0';
size_t len = tox_self_get_name_size(m);
selfname[len] = 0;
write_to_log(msg->message, selfname, log, msg->type == OUT_ACTION);
}
write_to_log(msg->message, selfname, self->chatwin->log, msg->type == OUT_ACTION);
cqueue_mark_read(self, msg);
struct cqueue_msg *next = msg->next;
if (msg->prev == NULL) { /* root */
if (next)
if (next) {
next->prev = NULL;
}
free(msg);
q->root = next;
} else {
struct cqueue_msg *prev = msg->prev;
free(msg);
prev->next = next;
next->prev = prev;
}
free(msg);
return;
}
}
#define CQUEUE_TRY_SEND_INTERVAL 60
// We use knowledge of toxcore internals (bad!) to determine that if we haven't received a read receipt for a
// sent packet after this amount of time, the connection has been severed and the packet needs to be re-sent.
#define TRY_SEND_TIMEOUT 32
/* Tries to send the oldest unsent message in queue. */
/*
* Marks all timed out messages in queue as unsent.
*/
static void cqueue_check_timeouts(struct cqueue_msg *msg)
{
while (msg) {
if (timed_out(msg->last_send_try, TRY_SEND_TIMEOUT)) {
msg->receipt = -1;
}
msg = msg->next;
}
}
/*
* Tries to send all messages in the send queue in sequential order.
* If a message fails to send the function will immediately return.
*/
void cqueue_try_send(ToxWindow *self, Tox *m)
{
struct chat_queue *q = self->chatwin->cqueue;
struct cqueue_msg *msg = q->root;
if (!msg)
return;
while (msg) {
if (msg->receipt != -1) {
// we can no longer try to send unsent messages until we get receipts for our previous sent
// messages, but we continue to iterate the list, checking timestamps for any further
// successfully sent messages that have not yet gotten a receipt.
cqueue_check_timeouts(msg);
return;
}
if (msg->receipt != 0 && !timed_out(msg->last_send_try, CQUEUE_TRY_SEND_INTERVAL))
return;
TOX_ERR_FRIEND_SEND_MESSAGE err;
Tox_Message_Type type = msg->type == OUT_MSG ? TOX_MESSAGE_TYPE_NORMAL : TOX_MESSAGE_TYPE_ACTION;
uint32_t receipt = tox_friend_send_message(m, self->num, type, (uint8_t *) msg->message, msg->len, &err);
uint32_t receipt = 0;
if (err != TOX_ERR_FRIEND_SEND_MESSAGE_OK) {
return;
}
TOX_MESSAGE_TYPE type = msg->type == OUT_MSG ? TOX_MESSAGE_TYPE_NORMAL : TOX_MESSAGE_TYPE_ACTION;
receipt = tox_friend_send_message(m, self->num, type, (uint8_t *) msg->message, msg->len, NULL);
msg->last_send_try = get_unix_time();
msg->receipt = receipt;
return;
msg->receipt = receipt;
msg->last_send_try = get_unix_time();
msg = msg->next;
}
}

View File

@ -27,9 +27,9 @@ struct cqueue_msg {
char message[MAX_STR_SIZE];
size_t len;
int line_id;
uint8_t type;
uint32_t receipt;
time_t last_send_try;
uint8_t type;
int64_t receipt;
struct cqueue_msg *next;
struct cqueue_msg *prev;
};
@ -40,12 +40,15 @@ struct chat_queue {
};
void cqueue_cleanup(struct chat_queue *q);
void cqueue_add(struct chat_queue *q, const char *msg, size_t len, uint8_t type, uint32_t line_id);
void cqueue_add(struct chat_queue *q, const char *msg, size_t len, uint8_t type, int line_id);
/* Tries to send the oldest unsent message in queue. */
/*
* Tries to send all messages in the send queue in sequential order.
* If a message fails to send the function will immediately return.
*/
void cqueue_try_send(ToxWindow *self, Tox *m);
/* removes message with matching receipt from queue, writes to log and updates line to show the message was received. */
void cqueue_remove(ToxWindow *self, Tox *m, uint32_t receipt);
#endif /* #define MESSAGE_QUEUE_H */
#endif /* MESSAGE_QUEUE_H */

View File

@ -20,42 +20,44 @@
*
*/
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <limits.h>
#include <dirent.h>
#if defined(__FreeBSD__)
#include <netinet/in.h>
#include <sys/socket.h>
#else
#include <arpa/inet.h>
#endif
#include <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include "toxic.h"
#include "windows.h"
#include "file_transfers.h"
#include "misc_tools.h"
#include "settings.h"
#include "file_transfers.h"
#include "toxic.h"
#include "windows.h"
extern ToxWindow *prompt;
extern struct user_settings *user_settings;
void clear_screen(void)
{
printf("\033[2J\033[1;1H");
}
void hst_to_net(uint8_t *num, uint16_t numbytes)
{
#ifndef WORDS_BIGENDIAN
uint32_t i;
uint8_t buff[numbytes];
uint8_t *buff = malloc(numbytes);
for (i = 0; i < numbytes; ++i) {
if (buff == NULL) {
return;
}
for (uint32_t i = 0; i < numbytes; ++i) {
buff[i] = num[numbytes - i - 1];
}
memcpy(num, buff, numbytes);
free(buff);
#endif
return;
}
time_t get_unix_time(void)
@ -69,6 +71,22 @@ int timed_out(time_t timestamp, time_t timeout)
return timestamp + timeout <= get_unix_time();
}
/* Attempts to sleep the caller's thread for `usec` microseconds */
void sleep_thread(long int usec)
{
struct timespec req;
struct timespec rem;
req.tv_sec = 0;
req.tv_nsec = usec * 1000L;
if (nanosleep(&req, &rem) == -1) {
if (nanosleep(&rem, NULL) == -1) {
fprintf(stderr, "nanosleep() returned -1\n");
}
}
}
/* Get the current local time */
struct tm *get_time(void)
{
@ -78,34 +96,44 @@ struct tm *get_time(void)
return timeinfo;
}
/*Puts the current time in buf in the format of [HH:mm:ss] */
void get_time_str(char *buf, int bufsize)
/* Puts the current time in buf in the format of specified by the config */
void get_time_str(char *buf, size_t bufsize)
{
if (buf == NULL || bufsize == 0) {
return;
}
*buf = 0;
if (user_settings->timestamps == TIMESTAMPS_OFF) {
buf[0] = '\0';
return;
}
const char *t = user_settings->timestamp_format;
strftime(buf, bufsize, t, get_time());
if (strftime(buf, bufsize, t, get_time()) == 0) {
strftime(buf, bufsize, TIMESTAMP_DEFAULT, get_time());
}
}
/* Converts seconds to string in format HH:mm:ss; truncates hours and minutes when necessary */
void get_elapsed_time_str(char *buf, int bufsize, time_t secs)
{
if (!secs)
if (!secs) {
return;
}
long int seconds = secs % 60;
long int minutes = (secs % 3600) / 60;
long int hours = secs / 3600;
if (!minutes && !hours)
if (!minutes && !hours) {
snprintf(buf, bufsize, "%.2ld", seconds);
else if (!hours)
} else if (!hours) {
snprintf(buf, bufsize, "%ld:%.2ld", minutes, seconds);
else
} else {
snprintf(buf, bufsize, "%ld:%.2ld:%.2ld", hours, minutes, seconds);
}
}
/*
@ -117,11 +145,12 @@ void get_elapsed_time_str(char *buf, int bufsize, time_t secs)
*/
int hex_string_to_bin(const char *hex_string, size_t hex_len, char *output, size_t output_size)
{
if (output_size == 0 || hex_len != output_size * 2)
if (output_size == 0 || hex_len != output_size * 2) {
return -1;
}
for (size_t i = 0; i < output_size; ++i) {
sscanf(hex_string, "%2hhx", &output[i]);
sscanf(hex_string, "%2hhx", (unsigned char *)&output[i]);
hex_string += 2;
}
@ -130,18 +159,19 @@ int hex_string_to_bin(const char *hex_string, size_t hex_len, char *output, size
int hex_string_to_bytes(char *buf, int size, const char *keystr)
{
if (size % 2 != 0)
if (size % 2 != 0) {
return -1;
}
int i, res;
const char *pos = keystr;
for (i = 0; i < size; ++i) {
res = sscanf(pos, "%2hhx", &buf[i]);
for (size_t i = 0; i < size; ++i) {
int res = sscanf(pos, "%2hhx", (unsigned char *)&buf[i]);
pos += 2;
if (res == EOF || res < 1)
if (res == EOF || res < 1) {
return -1;
}
}
return 0;
@ -154,13 +184,31 @@ int hex_string_to_bytes(char *buf, int size, const char *keystr)
*/
int bin_id_to_string(const char *bin_id, size_t bin_id_size, char *output, size_t output_size)
{
if (bin_id_size != TOX_ADDRESS_SIZE || output_size < (TOX_ADDRESS_SIZE * 2 + 1))
if (bin_id_size != TOX_ADDRESS_SIZE || output_size < (TOX_ADDRESS_SIZE * 2 + 1)) {
return -1;
}
size_t i;
for (i = 0; i < TOX_ADDRESS_SIZE; ++i)
for (size_t i = 0; i < TOX_ADDRESS_SIZE; ++i) {
snprintf(&output[i * 2], output_size - (i * 2), "%02X", bin_id[i] & 0xff);
}
return 0;
}
/* Converts a binary representation of a Tox public key into a string.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int bin_pubkey_to_string(const uint8_t *bin_pubkey, size_t bin_pubkey_size, char *output, size_t output_size)
{
if (bin_pubkey_size != TOX_PUBLIC_KEY_SIZE || output_size < (TOX_PUBLIC_KEY_SIZE * 2 + 1)) {
return -1;
}
for (size_t i = 0; i < TOX_PUBLIC_KEY_SIZE; ++i) {
snprintf(&output[i * 2], output_size - (i * 2), "%02X", bin_pubkey[i] & 0xff);
}
return 0;
}
@ -168,8 +216,9 @@ int bin_id_to_string(const char *bin_id, size_t bin_id_size, char *output, size_
/* Returns 1 if the string is empty, 0 otherwise */
int string_is_empty(const char *string)
{
if (!string)
if (!string) {
return true;
}
return string[0] == '\0';
}
@ -177,8 +226,9 @@ int string_is_empty(const char *string)
/* Returns 1 if the string is empty, 0 otherwise */
int wstring_is_empty(const wchar_t *string)
{
if (!string)
if (!string) {
return true;
}
return string[0] == L'\0';
}
@ -188,11 +238,13 @@ int mbs_to_wcs_buf(wchar_t *buf, const char *string, size_t n)
{
size_t len = mbstowcs(NULL, string, 0) + 1;
if (n < len)
if (n < len) {
return -1;
}
if ((len = mbstowcs(buf, string, n)) == (size_t) - 1)
if ((len = mbstowcs(buf, string, n)) == (size_t) - 1) {
return -1;
}
return len;
}
@ -202,11 +254,13 @@ int wcs_to_mbs_buf(char *buf, const wchar_t *string, size_t n)
{
size_t len = wcstombs(NULL, string, 0) + 1;
if (n < len)
if (n < len) {
return -1;
}
if ((len = wcstombs(buf, string, n)) == (size_t) - 1)
if ((len = wcstombs(buf, string, n)) == (size_t) - 1) {
return -1;
}
return len;
}
@ -217,41 +271,67 @@ int qsort_strcasecmp_hlpr(const void *str1, const void *str2)
return strcasecmp((const char *) str1, (const char *) str2);
}
/* Returns 1 if nick is valid, 0 if not. A valid toxic nick:
- cannot be empty
- cannot start with a space
- must not contain a forward slash (for logfile naming purposes)
- must not contain contiguous spaces
- must not contain a newline or tab seqeunce */
int valid_nick(const char *nick)
/* case-insensitive string compare function for use with qsort */
int qsort_ptr_char_array_helper(const void *str1, const void *str2)
{
if (!nick[0] || nick[0] == ' ')
return 0;
return strcasecmp(*(char **)str1, *(char **)str2);
}
int i;
static const char invalid_chars[] = {'/', '\n', '\t', '\v', '\r', '\0'};
for (i = 0; nick[i]; ++i) {
if ((nick[i] == ' ' && nick[i + 1] == ' ')
|| nick[i] == '/'
|| nick[i] == '\n'
|| nick[i] == '\t'
|| nick[i] == '\v'
|| nick[i] == '\r')
/*
* Helper function for `valid_nick()`.
*
* Returns true if `ch` is not in the `invalid_chars` array.
*/
static bool is_valid_char(char ch)
{
char tmp;
return 0;
for (size_t i = 0; (tmp = invalid_chars[i]); ++i) {
if (tmp == ch) {
return false;
}
}
return 1;
return true;
}
/* Returns true if nick is valid.
*
* A valid toxic nick:
* - cannot be empty
* - cannot start with a space
* - must not contain a forward slash (for logfile naming purposes)
* - must not contain contiguous spaces
* - must not contain a newline or tab seqeunce
*/
bool valid_nick(const char *nick)
{
if (!nick[0] || nick[0] == ' ') {
return false;
}
for (size_t i = 0; nick[i]; ++i) {
char ch = nick[i];
if ((ch == ' ' && nick[i + 1] == ' ') || !is_valid_char(ch)) {
return false;
}
}
return true;
}
/* Converts all newline/tab chars to spaces (use for strings that should be contained to a single line) */
void filter_str(char *str, size_t len)
{
size_t i;
for (size_t i = 0; i < len; ++i) {
char ch = str[i];
for (i = 0; i < len; ++i) {
if (str[i] == '\n' || str[i] == '\r' || str[i] == '\t' || str[i] == '\v' || str[i] == '\0')
if (!is_valid_char(ch) || str[i] == '\0') {
str[i] = ' ';
}
}
}
@ -263,22 +343,26 @@ size_t get_file_name(char *namebuf, size_t bufsize, const char *pathname)
int len = strlen(pathname) - 1;
char *path = strdup(pathname);
if (path == NULL)
if (path == NULL) {
exit_toxic_err("failed in get_file_name", FATALERR_MEMORY);
}
while (len >= 0 && pathname[len] == '/')
while (len >= 0 && pathname[len] == '/') {
path[len--] = '\0';
}
char *finalname = strdup(path);
if (finalname == NULL)
if (finalname == NULL) {
exit_toxic_err("failed in get_file_name", FATALERR_MEMORY);
}
const char *basenm = strrchr(path, '/');
if (basenm != NULL) {
if (basenm[1])
if (basenm[1]) {
strcpy(finalname, &basenm[1]);
}
}
snprintf(namebuf, bufsize, "%s", finalname);
@ -295,13 +379,15 @@ size_t get_file_name(char *namebuf, size_t bufsize, const char *pathname)
*/
size_t get_base_dir(const char *path, size_t path_len, char *dir)
{
if (path_len == 0 || path == NULL)
if (path_len == 0 || path == NULL) {
return 0;
}
size_t dir_len = char_rfind(path, '/', path_len);
if (dir_len != 0 && dir_len < path_len)
++dir_len; /* Leave trailing slash */
if (dir_len != 0 && dir_len < path_len) {
++dir_len; /* Leave trailing slash */
}
memcpy(dir, path, dir_len);
dir[dir_len] = '\0';
@ -314,8 +400,9 @@ void str_to_lower(char *str)
{
int i;
for (i = 0; str[i]; ++i)
for (i = 0; str[i]; ++i) {
str[i] = tolower(str[i]);
}
}
/* puts friendnum's nick in buf, truncating at TOXIC_MAX_NAME_LENGTH if necessary.
@ -323,45 +410,71 @@ void str_to_lower(char *str)
Returns nick len */
size_t get_nick_truncate(Tox *m, char *buf, uint32_t friendnum)
{
size_t len = tox_friend_get_name_size(m, friendnum, NULL);
Tox_Err_Friend_Query err;
size_t len = tox_friend_get_name_size(m, friendnum, &err);
if (len == 0) {
strcpy(buf, UNKNOWN_NAME);
len = strlen(UNKNOWN_NAME);
if (err != TOX_ERR_FRIEND_QUERY_OK) {
goto on_error;
} else {
tox_friend_get_name(m, friendnum, (uint8_t *) buf, NULL);
if (!tox_friend_get_name(m, friendnum, (uint8_t *) buf, NULL)) {
goto on_error;
}
}
len = MIN(len, TOXIC_MAX_NAME_LENGTH - 1);
buf[len] = '\0';
filter_str(buf, len);
return len;
on_error:
strcpy(buf, UNKNOWN_NAME);
len = strlen(UNKNOWN_NAME);
buf[len] = '\0';
return len;
}
/* same as get_nick_truncate but for groupchats */
int get_group_nick_truncate(Tox *m, char *buf, int peernum, int groupnum)
/* same as get_nick_truncate but for conferences */
int get_conference_nick_truncate(Tox *m, char *buf, uint32_t peernum, uint32_t conferencenum)
{
int len = tox_group_peername(m, groupnum, peernum, (uint8_t *) buf);
Tox_Err_Conference_Peer_Query err;
size_t len = tox_conference_peer_get_name_size(m, conferencenum, peernum, &err);
if (len == -1) {
strcpy(buf, UNKNOWN_NAME);
len = strlen(UNKNOWN_NAME);
if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
goto on_error;
} else {
if (!tox_conference_peer_get_name(m, conferencenum, peernum, (uint8_t *) buf, NULL)) {
goto on_error;
}
}
len = MIN(len, TOXIC_MAX_NAME_LENGTH - 1);
buf[len] = '\0';
filter_str(buf, len);
return len;
on_error:
strcpy(buf, UNKNOWN_NAME);
len = strlen(UNKNOWN_NAME);
buf[len] = '\0';
return len;
}
/* copies data to msg buffer.
/* copies data to msg buffer, removing return characters.
returns length of msg, which will be no larger than size-1 */
size_t copy_tox_str(char *msg, size_t size, const char *data, size_t length)
{
size_t len = MIN(length, size - 1);
memcpy(msg, data, len);
msg[len] = '\0';
return len;
size_t i;
size_t j = 0;
for (i = 0; (i < length) && (j < size - 1); ++i) {
if (data[i] != '\r') {
msg[j++] = data[i];
}
}
msg[j] = '\0';
return j;
}
/* returns index of the first instance of ch in s starting at idx.
@ -375,8 +488,9 @@ int char_find(int idx, const char *s, char ch)
int i = idx;
for (i = idx; s[i]; ++i) {
if (s[i] == ch)
if (s[i] == ch) {
break;
}
}
return i;
@ -393,8 +507,9 @@ int char_rfind(const char *s, char ch, int len)
int i = 0;
for (i = len; i > 0; --i) {
if (s[i] == ch)
if (s[i] == ch) {
break;
}
}
return i;
@ -429,45 +544,59 @@ bool file_exists(const char *path)
return stat(path, &s) == 0;
}
/*
* Checks the file type path points to and returns a File_Type enum value.
*
* Returns FILE_TYPE_DIRECTORY if path points to a directory.
* Returns FILE_TYPE_REGULAR if path points to a regular file.
* Returns FILE_TYPE_OTHER on any other result, including an invalid path.
*/
File_Type file_type(const char *path)
{
struct stat s;
if (stat(path, &s) == -1) {
return FILE_TYPE_OTHER;
}
switch (s.st_mode & S_IFMT) {
case S_IFDIR:
return FILE_TYPE_DIRECTORY;
case S_IFREG:
return FILE_TYPE_REGULAR;
default:
return FILE_TYPE_OTHER;
}
}
/* returns file size. If file doesn't exist returns 0. */
off_t file_size(const char *path)
{
struct stat st;
if (stat(path, &st) == -1)
if (stat(path, &st) == -1) {
return 0;
}
return st.st_size;
}
/* compares the first size bytes of fp to signature.
Returns 0 if they are the same, 1 if they differ, and -1 on error.
On success this function will seek back to the beginning of fp */
int check_file_signature(const char *signature, size_t size, FILE *fp)
{
char buf[size];
if (fread(buf, size, 1, fp) != 1)
return -1;
int ret = memcmp(signature, buf, size);
if (fseek(fp, 0L, SEEK_SET) == -1)
return -1;
return ret == 0 ? 0 : 1;
}
/* sets window title in tab bar. */
void set_window_title(ToxWindow *self, const char *title, int len)
{
if (len <= 0 || !title) {
return;
}
char cpy[TOXIC_MAX_NAME_LENGTH + 1];
if (self->is_groupchat) /* keep groupnumber in title */
snprintf(cpy, sizeof(cpy), "%d %s", self->num, title);
else
if (self->type == WINDOW_TYPE_CONFERENCE) { /* keep conferencenumber in title for invites */
snprintf(cpy, sizeof(cpy), "%u %s", self->num, title);
} else {
snprintf(cpy, sizeof(cpy), "%s", title);
}
if (len > MAX_WINDOW_NAME_LENGTH) {
strcpy(&cpy[MAX_WINDOW_NAME_LENGTH - 3], "...");
@ -477,35 +606,49 @@ void set_window_title(ToxWindow *self, const char *title, int len)
snprintf(self->name, sizeof(self->name), "%s", cpy);
}
/* Return true if address appears to be a valid ipv4 address. */
bool is_ip4_address(const char *address)
/*
* Frees all members of a pointer array plus `arr`.
*/
void free_ptr_array(void **arr)
{
struct sockaddr_in s_addr;
return inet_pton(AF_INET, address, &(s_addr.sin_addr)) != 0;
if (arr == NULL) {
return;
}
void **tmp = arr;
while (*arr) {
free(*arr);
++arr;
}
free(tmp);
}
/* Return true if address roughly appears to be a valid ipv6 address.
/*
* Returns a null terminated array of `length` pointers. Each pointer is allocated `bytes` bytes.
* Returns NULL on failure.
*
* TODO: Improve this function (inet_pton behaves strangely with ipv6).
* for now the only guarantee is that it won't return true if the
* address is a domain or ipv4 address, and should only be used if you're
* reasonably sure that the address is one of the three (ipv4, ipv6 or a domain).
* The caller is responsible for freeing the array with `free_ptr_array`.
*/
bool is_ip6_address(const char *address)
void **malloc_ptr_array(size_t length, size_t bytes)
{
size_t i;
size_t num_colons = 0;
char ch = 0;
void **arr = malloc((length + 1) * sizeof(void *));
for (i = 0; (ch = address[i]); ++i) {
if (ch == '.') {
return false;
}
if (arr == NULL) {
return NULL;
}
if (ch == ':') {
++num_colons;
for (size_t i = 0; i < length; ++i) {
arr[i] = malloc(bytes);
if (arr[i] == NULL) {
free_ptr_array(arr);
return NULL;
}
}
return num_colons > 1 && num_colons < 8;
arr[length] = NULL;
return arr;
}

View File

@ -24,8 +24,8 @@
#include <sys/stat.h>
#include "windows.h"
#include "toxic.h"
#include "windows.h"
#ifndef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
@ -39,6 +39,17 @@
#define net_to_host(x, y) hst_to_net(x, y)
#endif
#define UNUSED_VAR(x) ((void) x)
typedef enum File_Type {
FILE_TYPE_REGULAR,
FILE_TYPE_DIRECTORY,
FILE_TYPE_OTHER,
} File_Type;
void clear_screen(void);
void hst_to_net(uint8_t *num, uint16_t numbytes);
/*
@ -60,11 +71,18 @@ int hex_string_to_bytes(char *buf, int size, const char *keystr);
*/
int bin_id_to_string(const char *bin_id, size_t bin_id_size, char *output, size_t output_size);
/* Converts a binary representation of a Tox public key into a string.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int bin_pubkey_to_string(const uint8_t *bin_pubkey, size_t bin_pubkey_size, char *output, size_t output_size);
/* get the current unix time (not thread safe) */
time_t get_unix_time(void);
/* Puts the current time in buf in the format of [HH:mm:ss] (not thread safe) */
void get_time_str(char *buf, int bufsize);
/* Puts the current time in buf in the format of specified by the config */
void get_time_str(char *buf, size_t bufsize);
/* Converts seconds to string in format HH:mm:ss; truncates hours and minutes when necessary */
void get_elapsed_time_str(char *buf, int bufsize, time_t secs);
@ -78,31 +96,40 @@ int string_is_empty(const char *string);
/* Same as above but for wide character strings */
int wstring_is_empty(const wchar_t *string);
/* convert a multibyte string to a wide character string (must provide buffer) */
/* converts a multibyte string to a wide character string (must provide buffer) */
int char_to_wcs_buf(wchar_t *buf, const char *string, size_t n);
/* converts wide character string into a multibyte string and puts in buf. */
int wcs_to_mbs_buf(char *buf, const wchar_t *string, size_t n);
/* convert a multibyte string to a wide character string and puts in buf) */
/* converts a multibyte string to a wide character string and puts in buf) */
int mbs_to_wcs_buf(wchar_t *buf, const char *string, size_t n);
/* Returns 1 if connection has timed out, 0 otherwise */
int timed_out(time_t timestamp, time_t timeout);
/* Attempts to sleep the caller's thread for `usec` microseconds */
void sleep_thread(long int usec);
/* Colours the window tab according to type. Beeps if is_beep is true */
void alert_window(ToxWindow *self, int type, bool is_beep);
/* case-insensitive string compare function for use with qsort */
int qsort_strcasecmp_hlpr(const void *str1, const void *str2);
/* Returns 1 if nick is valid, 0 if not. A valid toxic nick:
- cannot be empty
- cannot start with a space
- must not contain a forward slash (for logfile naming purposes)
- must not contain contiguous spaces
- must not contain a newline or tab seqeunce */
int valid_nick(const char *nick);
/* case-insensitive string compare function for use with qsort */
int qsort_ptr_char_array_helper(const void *str1, const void *str2);
/* Returns true if nick is valid.
*
* A valid toxic nick:
* - cannot be empty
* - cannot start with a space
* - must not contain a forward slash (for logfile naming purposes)
* - must not contain contiguous spaces
* - must not contain a newline or tab seqeunce
*/
bool valid_nick(const char *nick);
/* Converts all newline/tab chars to spaces (use for strings that should be contained to a single line) */
void filter_str(char *str, size_t len);
@ -125,8 +152,8 @@ void str_to_lower(char *str);
Returns nick len on success, -1 on failure */
size_t get_nick_truncate(Tox *m, char *buf, uint32_t friendnum);
/* same as get_nick_truncate but for groupchats */
int get_group_nick_truncate(Tox *m, char *buf, int peernum, int groupnum);
/* same as get_nick_truncate but for conferences */
int get_conference_nick_truncate(Tox *m, char *buf, uint32_t peernum, uint32_t conferencenum);
/* copies data to msg buffer.
returns length of msg, which will be no larger than size-1 */
@ -146,28 +173,32 @@ void bytes_convert_str(char *buf, int size, uint64_t bytes);
/* checks if a file exists. Returns true or false */
bool file_exists(const char *path);
/*
* Checks the file type path points to and returns a File_Type enum value.
*
* Returns FILE_TYPE_DIRECTORY if path points to a directory.
* Returns FILE_TYPE_REGULAR if path points to a regular file.
* Returns FILE_TYPE_OTHER on any other result, including an invalid path.
*/
File_Type file_type(const char *path);
/* returns file size. If file doesn't exist returns 0. */
off_t file_size(const char *path);
/* compares the first size bytes of fp and signature.
Returns 0 if they are the same, 1 if they differ, and -1 on error.
On success this function will seek back to the beginning of fp */
int check_file_signature(const char *signature, size_t size, FILE *fp);
/* sets window title in tab bar. */
void set_window_title(ToxWindow *self, const char *title, int len);
/* Return true if address appears to be a valid ipv4 address. */
bool is_ip4_address(const char *address);
/* Return true if address roughly appears to be a valid ipv6 address.
*
* TODO: Improve this function (inet_pton behaves strangely with ipv6).
* for now the only guarantee is that it won't return true if the
* address is a domain or ipv4 address, and should only be used if you're
* reasonably sure that the address is one of the three (ipv4, ipv6 or a domain).
/*
* Frees all members of a pointer array plus `arr`.
*/
bool is_ip6_address(const char *address);
void free_ptr_array(void **arr);
#endif /* #define MISC_TOOLS_H */
/*
* Returns a null terminated array of `length` pointers. Each pointer is allocated `bytes` bytes.
* Returns NULL on failure.
*
* The caller is responsible for freeing the array with `free_ptr_array`.
*/
void **malloc_ptr_array(size_t length, size_t bytes);
#endif /* MISC_TOOLS_H */

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