1
0
mirror of https://github.com/Tha14/toxic.git synced 2025-06-26 21:16:44 +02:00

Compare commits

...

106 Commits

Author SHA1 Message Date
0239509439 Bump version to 0.11.3 and update toxcore version in static build script 2021-12-24 14:21:26 -05:00
602d9d97c1 Send file control cancel when we block or delete a friend 2021-12-22 14:53:09 -05:00
6f8f6f0ac5 Implement file transfer queue for offline friends
File transfers initiated for offline friends are now
added to a queue and initiated all at once when the friend
appears online.
2021-12-22 14:27:28 -05:00
8d58e8f4d6 chore: Fix BUILD format (we check this on toktok-stack now). 2021-12-21 12:23:01 +00:00
b6f892edf5 Remove unnecessary define for keeping track of string array size 2021-12-15 13:39:11 -05:00
f91d10c831 Add missing ifdef 2021-12-13 12:37:51 -05:00
3f18c6f8de cleanup: fix some uninitialized memory warnings and clarify some logic 2021-12-13 12:33:58 -05:00
bf1e1b73fc Do proper error handling for a few malloc calls 2021-12-12 11:30:27 -05:00
3eac65036c Make infer print full error log 2021-12-11 20:13:31 -05:00
85f9ec6b9a chore: Add "infer" static analysis action on CI. 2021-12-11 23:55:29 +00:00
5230616a9d chore: Fix CI: don't install bootstrap daemon. 2021-12-11 23:01:47 +00:00
8e0e318df1 chore: Add github CI workflow. 2021-12-11 22:47:54 +00:00
701c0e1e94 cleanup: Reduce the amount of "extern" in .c files.
There is no check at all that the types of externs actually match the
type of the definition. This just accidentally was not a problem, but now
the compiler helps a bit in keeping it that way.
2021-12-11 22:35:51 +00:00
71f6a8d4d6 cleanup: Avoid casting away constness from pointers. 2021-12-11 22:22:25 +00:00
1cace1e81d Do exact length check for string passed to tox_pk_string_to_bytes 2021-12-11 17:13:30 -05:00
3030cda47e Fix possible null dereference
game invite info needs to be set after calloc in case it fails
2021-12-10 13:44:33 -05:00
2946e0dc56 Zero-init coordinates
Not strictly necessary, but makes static analysis happy
2021-12-10 13:09:47 -05:00
da90ca8b74 Fix bazel build for games. 2021-12-10 17:52:43 +00:00
17e5cc1147 Merge branch 'toktok' 2021-12-08 14:38:39 -05:00
d02ac24de8 Fix regression causing typing notifications to break
Also a small refactor so we use less duplicate code and reduce mutex usage
2021-12-07 22:14:32 -05:00
66e4c590dc Fix some lazy/incorrect code in cmd_add()
This fixes a rare but possible scenario where a name@domain lookup that's
longer than a tox id won't work. It also fixes custom messages for domain
lookups
2021-12-07 13:27:38 -05:00
8176b43880 Update static build script
Bump curl and toxcore versions
2021-12-06 11:42:53 -05:00
b88dc9fda1 Bump version to 0.11.2 2021-12-06 11:08:17 -05:00
d7a19791b4 Remove CHANGELOG.md
This hasn't been updated in ages
2021-12-06 10:59:00 -05:00
e2c8497da9 Cleanup code around tox ID/pk conversion functions 2021-12-06 10:46:19 -05:00
afbd185222 /add command no longer requires quotes around the message 2021-12-06 10:06:49 -05:00
64794e14cd chore: Use toktok-stack 0.0.23 for cirrus builds. 2021-12-06 00:40:44 +00:00
e9a0a30408 Some documentation improvements 2021-12-05 17:03:42 -05:00
8aa57b0539 Fix issue with audio VAD threshold
The default VAD was always being set to 0. We now use the
value provided by the config file if defined
2021-12-05 16:36:13 -05:00
7b734f3996 Make tab completion case sensitive 2021-12-04 16:34:15 -05:00
27e20d6f44 Fix possible buffer overrun in python API 2021-11-26 18:57:40 -05:00
f39da4d7f2 chore: Add a bazel test that calls toxic with --help.
This way we at least know it doesn't crash on load. Some code runs. We'll
want some real tests at some point, but this ensures *something* works.
2021-11-26 23:57:39 +00:00
af70c3d660 chore: Remove travis; add cirrus ci.
We still need a GitHub workflow to test the "make" build. For now, we only
test toktok-stack and the bazel build.
2021-11-26 23:52:39 +00:00
58d0bd0663 Allow empty notes 2021-11-26 09:03:43 -05:00
090fcfffe3 Fix a couple data races 2021-11-26 08:51:38 -05:00
1cba726bb8 Show self connection type and small UI makeover
Connection type is now separated from status in the top status bar
2021-11-25 23:05:33 -05:00
50a074ed22 Remove some unnecessary calls to flag_interface_refresh() 2021-11-21 20:16:02 -05:00
3fddc410b3 Reduce sound notify polling by factor of 10
Polling 100 times per second is excessive. This should help reduce
Toxic's resource usage without a noticable difference to the user.
2021-11-21 14:52:09 -05:00
0a708e4a2e Make sure conference audio calls are properly flagged
This fixes a bug causing the conference audio noise animations
and indicators from properly displaying
2021-11-20 10:16:47 -05:00
b7002ef3f0 Allow ncurses refresh rate to be set dynamically
This allows us to have a higher refresh rate only when necessary (e.g. games)
2021-11-20 09:49:04 -05:00
1803da85c1 Refactor unread message flagging
This fixes an issue where the interface wasn't able to update
when the unread message flag changed. It also cleans up some
ugly code
2021-11-19 22:54:35 -05:00
f3f81111c8 Greatly reduce redundant window refreshing
This should substantially reduce CPU usage and possibly fix some
issues with interface jittering/flashing
2021-11-19 17:54:35 -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
81 changed files with 11112 additions and 2260 deletions

13
.cirrus.yml Normal file
View File

@ -0,0 +1,13 @@
---
cirrus-ci_task:
container:
image: toxchat/toktok-stack:0.0.31-third_party
cpu: 2
memory: 2G
configure_script:
- /src/workspace/tools/inject-repo toxic
test_all_script:
- cd /src/workspace && bazel test -k
--remote_http_cache=http://$CIRRUS_HTTP_CACHE_HOST
--config=release
//toxic/...

View File

@ -11,7 +11,8 @@ branches:
protection:
required_status_checks:
contexts:
- Codacy/PR Quality Review
- CodeFactor
- Travis CI - Pull Request
- build
- cirrus-ci
- Codacy Static Code Analysis
- code-review/reviewable
- infer

106
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,106 @@
name: ci
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run:
sudo apt-get update &&
sudo apt-get install -y --no-install-recommends
libalut-dev
libconfig-dev
libcurl4-gnutls-dev
libnotify-dev
libopenal-dev
libopus-dev
libqrencode-dev
libsodium-dev
libvpx-dev
libx11-dev
python3-dev
pkg-config &&
git clone --depth=1 https://github.com/TokTok/c-toxcore &&
cd c-toxcore &&
cmake . -B_build -DBOOTSTRAP_DAEMON=OFF &&
cd _build &&
make -j4 &&
sudo make install
- name: Build toxic
run: make -j4
infer:
runs-on: ubuntu-latest
container: toxchat/infer
steps:
- name: Install git
run:
apt-get update &&
DEBIAN_FRONTEND=noninteractive
apt-get install -y --no-install-recommends
git
- name: Install dependencies
run:
apt-get update &&
apt-get install -y --no-install-recommends
cmake
g++
libalut-dev
libconfig-dev
libcurl4-gnutls-dev
libncurses-dev
libnotify-dev
libopenal-dev
libopus-dev
libqrencode-dev
libsodium-dev
libvpx-dev
libx11-dev
make
python3-dev
pkg-config &&
git clone --depth=1 https://github.com/TokTok/c-toxcore &&
cd c-toxcore &&
cmake . -B_build -DBOOTSTRAP_DAEMON=OFF &&
cd _build &&
make -j4 &&
make install
- uses: actions/checkout@v2
- name: Run infer
run:
infer --no-progress-bar -- cc src/*.c
-fsyntax-only
$(python3-config --includes --ldflags)
$(pkg-config --cflags --libs
freealut
libconfig
libcurl
libnotify
libpng
libqrencode
ncurses
openal
python3
toxcore
vpx
x11)
-DAUDIO
-DBOX_NOTIFY
-DGAMES
-DPACKAGE_DATADIR='""'
-DPYTHON
-DQRCODE
-DSOUND_NOTIFY
-DVIDEO
- name: Print log
run:
cat /__w/toxic/toxic/infer-out/report.txt

3
.gitignore vendored
View File

@ -17,3 +17,6 @@ build/toxic
build/*.o
build/*.d
apidoc/python/build
*.vim
*.tox
*.nvim*

View File

@ -1,60 +0,0 @@
---
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)

View File

@ -18,6 +18,7 @@ cc_binary(
copts = [
"-std=gnu99",
"-DAUDIO",
"-DGAMES",
"-DPACKAGE_DATADIR='\"data\"'",
"-DPYTHON",
"-DQRCODE",
@ -39,3 +40,10 @@ cc_binary(
"//tools/config:osx": [],
}),
)
sh_test(
name = "toxic_test",
size = "small",
srcs = [":toxic"],
args = ["--help"],
)

View File

@ -1,503 +0,0 @@
# Change Log
## [Unreleased](https://github.com/JFreegman/toxic/tree/HEAD)
[Full Changelog](https://github.com/JFreegman/toxic/compare/v0.7.0...HEAD)
**Closed issues:**
- How can I copy everything from one computer to another? [\#391](https://github.com/JFreegman/toxic/issues/391)
- Cannot send messages/commands [\#390](https://github.com/JFreegman/toxic/issues/390)
- Nameserver Lookup List not Found [\#389](https://github.com/JFreegman/toxic/issues/389)
- ERROR: toxini file 'tox.ini' not found [\#388](https://github.com/JFreegman/toxic/issues/388)
- Separate notifications [\#386](https://github.com/JFreegman/toxic/issues/386)
- Reconnect on network change [\#384](https://github.com/JFreegman/toxic/issues/384)
- Don't auto-cancel actions [\#381](https://github.com/JFreegman/toxic/issues/381)
- How to export your profile? [\#377](https://github.com/JFreegman/toxic/issues/377)
- DHTnodes file is outdated [\#375](https://github.com/JFreegman/toxic/issues/375)
- Toxic fails to initialize if ~/.config directory doesn't exist [\#372](https://github.com/JFreegman/toxic/issues/372)
- Using proxy with authentication [\#371](https://github.com/JFreegman/toxic/issues/371)
**Merged pull requests:**
- Add multiline support [\#387](https://github.com/JFreegman/toxic/pull/387) ([mphe](https://github.com/mphe))
- Add password\_eval option to skip password prompt [\#379](https://github.com/JFreegman/toxic/pull/379) ([FreakyPenguin](https://github.com/FreakyPenguin))
- sleep use tox\_iteration\_interval [\#374](https://github.com/JFreegman/toxic/pull/374) ([quininer](https://github.com/quininer))
- Fix \#372 - can't start with missing ~/.config [\#373](https://github.com/JFreegman/toxic/pull/373) ([wedge-jarrad](https://github.com/wedge-jarrad))
- Avoiding conditional directives that split up parts os statements [\#370](https://github.com/JFreegman/toxic/pull/370) ([RomeroMalaquias](https://github.com/RomeroMalaquias))
- update doc: DATA\_FILE is now `toxic\_profile.tox` [\#369](https://github.com/JFreegman/toxic/pull/369) ([nil0x42](https://github.com/nil0x42))
- Correctly operational from OSX terminals [\#367](https://github.com/JFreegman/toxic/pull/367) ([landswellsong](https://github.com/landswellsong))
## [v0.7.0](https://github.com/JFreegman/toxic/tree/v0.7.0) (2015-11-12)
[Full Changelog](https://github.com/JFreegman/toxic/compare/v0.6.1...v0.7.0)
**Implemented enhancements:**
- /myid doesn't show qrcode [\#326](https://github.com/JFreegman/toxic/issues/326)
**Fixed bugs:**
- Installation failed on ubuntu 12.04, package missing [\#279](https://github.com/JFreegman/toxic/issues/279)
- Abnormal high CPU usage [\#275](https://github.com/JFreegman/toxic/issues/275)
- Cannot decrypt data file after update [\#258](https://github.com/JFreegman/toxic/issues/258)
**Closed issues:**
- Compiling video\_device.c on FreeBSD [\#364](https://github.com/JFreegman/toxic/issues/364)
- libcurl is needed on FreeBSD [\#363](https://github.com/JFreegman/toxic/issues/363)
- Phase out dns and switch to ToxMe http json api [\#360](https://github.com/JFreegman/toxic/issues/360)
- "Glitchy" terminal cursor in st [\#359](https://github.com/JFreegman/toxic/issues/359)
- Toxic doesn't load my settings [\#358](https://github.com/JFreegman/toxic/issues/358)
- Does Toxic support proxy? [\#355](https://github.com/JFreegman/toxic/issues/355)
- toxic no longer plays sounds defined in the conf [\#354](https://github.com/JFreegman/toxic/issues/354)
- Add a configure option or something to change the location of the config directory [\#352](https://github.com/JFreegman/toxic/issues/352)
- Remove/Replace links to libtoxcore.so [\#349](https://github.com/JFreegman/toxic/issues/349)
- "No pending friend requests." while"Friend request has already been sent." [\#348](https://github.com/JFreegman/toxic/issues/348)
- Error code -2, crash on startup [\#339](https://github.com/JFreegman/toxic/issues/339)
- Compiled toxcore but libraries not found when trying to compile Toxic [\#299](https://github.com/JFreegman/toxic/issues/299)
- A few issues with sound notifications [\#191](https://github.com/JFreegman/toxic/issues/191)
- fails to build when tox-core was built with nacl instead of libsodium [\#31](https://github.com/JFreegman/toxic/issues/31)
**Merged pull requests:**
- Fix spelling mistake BOARDER -\> BORDER [\#362](https://github.com/JFreegman/toxic/pull/362) ([subliun](https://github.com/subliun))
- Fix compile for DragonFlyBSD [\#351](https://github.com/JFreegman/toxic/pull/351) ([mneumann](https://github.com/mneumann))
## [v0.6.1](https://github.com/JFreegman/toxic/tree/v0.6.1) (2015-08-28)
[Full Changelog](https://github.com/JFreegman/toxic/compare/v0.6.0...v0.6.1)
**Closed issues:**
- \[Invalid UTF-8\] [\#344](https://github.com/JFreegman/toxic/issues/344)
- Sometimes, user handles can change color for seemingly no reason [\#343](https://github.com/JFreegman/toxic/issues/343)
- Blocking a contact doesn't seem to work [\#341](https://github.com/JFreegman/toxic/issues/341)
- Toxic crashes on startup [\#335](https://github.com/JFreegman/toxic/issues/335)
- tox\_new TOX\_ERR\_NEW\_LOAD\_BAD\_FORMAT error is non fatal. [\#333](https://github.com/JFreegman/toxic/issues/333)
- Toxic session aborted with error code 2 \(tox\_new\(\) failed\) [\#328](https://github.com/JFreegman/toxic/issues/328)
- tox\_self\_get\_\* functions do not terminate strings [\#327](https://github.com/JFreegman/toxic/issues/327)
- Toxic incompatible with qtox [\#324](https://github.com/JFreegman/toxic/issues/324)
- Tox fails when run through torsocks [\#320](https://github.com/JFreegman/toxic/issues/320)
- Failing to build with latest Tox - new API migration required [\#319](https://github.com/JFreegman/toxic/issues/319)
- Avoid non-posix option in sed. [\#307](https://github.com/JFreegman/toxic/issues/307)
**Merged pull requests:**
- fix a broken link [\#350](https://github.com/JFreegman/toxic/pull/350) ([vinegret](https://github.com/vinegret))
- Makefile: allow overriding pkg-config [\#346](https://github.com/JFreegman/toxic/pull/346) ([ony](https://github.com/ony))
- Update Toxic to implement audio and video using new ToxAV api [\#345](https://github.com/JFreegman/toxic/pull/345) ([cnhenry](https://github.com/cnhenry))
- travis.yml: update dependencies [\#340](https://github.com/JFreegman/toxic/pull/340) ([Ansa89](https://github.com/Ansa89))
- Add localization system \(gettext\) [\#337](https://github.com/JFreegman/toxic/pull/337) ([Ansa89](https://github.com/Ansa89))
- Makefile: try to fix Tox/toxic\#307 [\#323](https://github.com/JFreegman/toxic/pull/323) ([Ansa89](https://github.com/Ansa89))
- Makefile: add uninstall target [\#322](https://github.com/JFreegman/toxic/pull/322) ([Ansa89](https://github.com/Ansa89))
## [v0.6.0](https://github.com/JFreegman/toxic/tree/v0.6.0) (2015-03-28)
[Full Changelog](https://github.com/JFreegman/toxic/compare/v0.5.2...v0.6.0)
**Closed issues:**
- Please do not force push to tox/toxic master branch. [\#311](https://github.com/JFreegman/toxic/issues/311)
- Import tox id [\#295](https://github.com/JFreegman/toxic/issues/295)
- openalut [\#287](https://github.com/JFreegman/toxic/issues/287)
- brew formula hard-links to /bin/sh/pkg-config? \(OS X\) [\#286](https://github.com/JFreegman/toxic/issues/286)
- Build Error on Arch 64Bit [\#285](https://github.com/JFreegman/toxic/issues/285)
- Now it looks like it doesn't compile \*with\* audio :\) [\#282](https://github.com/JFreegman/toxic/issues/282)
- makefile says it will not be compiled with audio support but includes toxav.h anyway. [\#281](https://github.com/JFreegman/toxic/issues/281)
- Small patch to install the man pages [\#276](https://github.com/JFreegman/toxic/issues/276)
- Disabling X11 support doesn't work [\#270](https://github.com/JFreegman/toxic/issues/270)
- Support arrow keys [\#265](https://github.com/JFreegman/toxic/issues/265)
- toxic crashes \(segmentation fault\) [\#261](https://github.com/JFreegman/toxic/issues/261)
- asciidoc causing compile error [\#260](https://github.com/JFreegman/toxic/issues/260)
- これはセグフォールトですか [\#259](https://github.com/JFreegman/toxic/issues/259)
- Verify ~/.config/tox permissions on startup [\#245](https://github.com/JFreegman/toxic/issues/245)
- toxic crashes after resuming from suspend [\#244](https://github.com/JFreegman/toxic/issues/244)
- Toxic does not compile on osx 10.9.3 [\#145](https://github.com/JFreegman/toxic/issues/145)
**Merged pull requests:**
- README.md: fix typo [\#318](https://github.com/JFreegman/toxic/pull/318) ([Ansa89](https://github.com/Ansa89))
- Makefile: be less aggressive when cleaning [\#316](https://github.com/JFreegman/toxic/pull/316) ([Ansa89](https://github.com/Ansa89))
- Move makefile into root directory [\#315](https://github.com/JFreegman/toxic/pull/315) ([Ansa89](https://github.com/Ansa89))
- Fixing couple leaking file descriptors [\#314](https://github.com/JFreegman/toxic/pull/314) ([al42and](https://github.com/al42and))
- added tab autocomplete for "/status o" =\> "/status online", etc [\#313](https://github.com/JFreegman/toxic/pull/313) ([hardlyeven](https://github.com/hardlyeven))
- Some cosmetics changes [\#310](https://github.com/JFreegman/toxic/pull/310) ([Ansa89](https://github.com/Ansa89))
- Openbsd [\#308](https://github.com/JFreegman/toxic/pull/308) ([henriqueleng](https://github.com/henriqueleng))
- Add support for custom timestamps in chat and logs. [\#303](https://github.com/JFreegman/toxic/pull/303) ([louipc](https://github.com/louipc))
- README.md: update download section [\#302](https://github.com/JFreegman/toxic/pull/302) ([Ansa89](https://github.com/Ansa89))
- Add INSTALL.md [\#301](https://github.com/JFreegman/toxic/pull/301) ([Ansa89](https://github.com/Ansa89))
- travis.yml: use latest libsodium stable [\#298](https://github.com/JFreegman/toxic/pull/298) ([Ansa89](https://github.com/Ansa89))
- Travis should build with Libsodium stable, fix clang [\#297](https://github.com/JFreegman/toxic/pull/297) ([urras](https://github.com/urras))
- Interface [\#296](https://github.com/JFreegman/toxic/pull/296) ([louipc](https://github.com/louipc))
- Correct filename comment from main.c to toxic.c [\#293](https://github.com/JFreegman/toxic/pull/293) ([Spagy](https://github.com/Spagy))
- Update for toxcore API break [\#292](https://github.com/JFreegman/toxic/pull/292) ([Ansa89](https://github.com/Ansa89))
- Fix some edge cases when obtaining paths [\#291](https://github.com/JFreegman/toxic/pull/291) ([dantok](https://github.com/dantok))
- Update DHT nodes again [\#290](https://github.com/JFreegman/toxic/pull/290) ([urras](https://github.com/urras))
- Update DHT node list [\#289](https://github.com/JFreegman/toxic/pull/289) ([urras](https://github.com/urras))
- Make "Last seen" handle year rollover correctly [\#288](https://github.com/JFreegman/toxic/pull/288) ([flussence](https://github.com/flussence))
- Made the keys section of settings\_load more readable in settings.c [\#284](https://github.com/JFreegman/toxic/pull/284) ([jpoler](https://github.com/jpoler))
- Destroy AL context before closing dhndl [\#283](https://github.com/JFreegman/toxic/pull/283) ([stal888](https://github.com/stal888))
- Darwin Build [\#280](https://github.com/JFreegman/toxic/pull/280) ([DomT4](https://github.com/DomT4))
- Fix Tox/toxic\#276 [\#278](https://github.com/JFreegman/toxic/pull/278) ([Ansa89](https://github.com/Ansa89))
- Makefile: revert back to mkdir [\#274](https://github.com/JFreegman/toxic/pull/274) ([Ansa89](https://github.com/Ansa89))
- Makefile: add toxic.desktop to install target [\#273](https://github.com/JFreegman/toxic/pull/273) ([Ansa89](https://github.com/Ansa89))
- Toxic.conf.exmaple: fix sound namefile [\#271](https://github.com/JFreegman/toxic/pull/271) ([Ansa89](https://github.com/Ansa89))
- Version: fix revision calculation [\#269](https://github.com/JFreegman/toxic/pull/269) ([Ansa89](https://github.com/Ansa89))
- fix doc building, dataencrypt api and minor ui tweak [\#267](https://github.com/JFreegman/toxic/pull/267) ([louipc](https://github.com/louipc))
- Change action messages indicator [\#264](https://github.com/JFreegman/toxic/pull/264) ([zetok](https://github.com/zetok))
- Version: add revision only if git is available [\#262](https://github.com/JFreegman/toxic/pull/262) ([Ansa89](https://github.com/Ansa89))
## [v0.5.2](https://github.com/JFreegman/toxic/tree/v0.5.2) (2014-09-29)
[Full Changelog](https://github.com/JFreegman/toxic/compare/v0.5.1...v0.5.2)
**Closed issues:**
- Failed to read log file [\#254](https://github.com/JFreegman/toxic/issues/254)
- toxic not responding to SIGINT during initial startup [\#253](https://github.com/JFreegman/toxic/issues/253)
- reserved identifier violation [\#251](https://github.com/JFreegman/toxic/issues/251)
- Fix signal handler [\#250](https://github.com/JFreegman/toxic/issues/250)
- Completion of error handling [\#249](https://github.com/JFreegman/toxic/issues/249)
- How to decline file sends? [\#247](https://github.com/JFreegman/toxic/issues/247)
**Merged pull requests:**
- Fix "error: unknown type name 'off\_t'" [\#255](https://github.com/JFreegman/toxic/pull/255) ([Ansa89](https://github.com/Ansa89))
- rm -rf -\> rm -f [\#252](https://github.com/JFreegman/toxic/pull/252) ([ghost](https://github.com/ghost))
- Update screenshot [\#246](https://github.com/JFreegman/toxic/pull/246) ([urras](https://github.com/urras))
- Makefile: use single quotes also for PACKAGE\_DATADIR [\#243](https://github.com/JFreegman/toxic/pull/243) ([Ansa89](https://github.com/Ansa89))
## [v0.5.1](https://github.com/JFreegman/toxic/tree/v0.5.1) (2014-09-19)
[Full Changelog](https://github.com/JFreegman/toxic/compare/v0.5.0...v0.5.1)
**Closed issues:**
- Support for faux offline messaging [\#233](https://github.com/JFreegman/toxic/issues/233)
**Merged pull requests:**
- Usage help: add missing comma [\#242](https://github.com/JFreegman/toxic/pull/242) ([Ansa89](https://github.com/Ansa89))
- Fix some 'clang --analyze' warnings [\#240](https://github.com/JFreegman/toxic/pull/240) ([s3erios](https://github.com/s3erios))
- Addition to Tox/toxic\#235 [\#238](https://github.com/JFreegman/toxic/pull/238) ([Ansa89](https://github.com/Ansa89))
- Some code simplification [\#236](https://github.com/JFreegman/toxic/pull/236) ([s3erios](https://github.com/s3erios))
- Add X11 option [\#235](https://github.com/JFreegman/toxic/pull/235) ([s3erios](https://github.com/s3erios))
## [v0.5.0](https://github.com/JFreegman/toxic/tree/v0.5.0) (2014-09-01)
[Full Changelog](https://github.com/JFreegman/toxic/compare/v0.4.7...v0.5.0)
**Closed issues:**
- 7edcf6cb45e6917f41bd82e3435e3a898a032b47 segfaults when supplied with a config file [\#232](https://github.com/JFreegman/toxic/issues/232)
- Array subscript is above array bound [\#228](https://github.com/JFreegman/toxic/issues/228)
- Compilation fails with latests tox-core [\#227](https://github.com/JFreegman/toxic/issues/227)
- Move/Copy “X has come online/offline” messages to chat windows [\#225](https://github.com/JFreegman/toxic/issues/225)
- MANDIR set for Linux [\#222](https://github.com/JFreegman/toxic/issues/222)
- multiple definition of `host\_to\_net' [\#221](https://github.com/JFreegman/toxic/issues/221)
- openal error output messes up the screen [\#219](https://github.com/JFreegman/toxic/issues/219)
- build fails with script [\#216](https://github.com/JFreegman/toxic/issues/216)
- UTF-8 Support [\#171](https://github.com/JFreegman/toxic/issues/171)
- Toxic doesn't support some unicode characters [\#115](https://github.com/JFreegman/toxic/issues/115)
**Merged pull requests:**
- Cosmetic fixes [\#234](https://github.com/JFreegman/toxic/pull/234) ([Ansa89](https://github.com/Ansa89))
- Reworked manpage build system [\#231](https://github.com/JFreegman/toxic/pull/231) ([Ansa89](https://github.com/Ansa89))
- Manpage [\#230](https://github.com/JFreegman/toxic/pull/230) ([louipc](https://github.com/louipc))
- toxic.conf.example: better formatting [\#229](https://github.com/JFreegman/toxic/pull/229) ([Ansa89](https://github.com/Ansa89))
- Fix Tox/toxic\#222 and reorganize cfg dir [\#226](https://github.com/JFreegman/toxic/pull/226) ([Ansa89](https://github.com/Ansa89))
- Add debug flag and update man page. [\#223](https://github.com/JFreegman/toxic/pull/223) ([louipc](https://github.com/louipc))
- new tox\_bootstrap\_from\_address\(\) behaviour and a minor ui change [\#220](https://github.com/JFreegman/toxic/pull/220) ([louipc](https://github.com/louipc))
- toxic.conf.5: Remove default config from man page [\#218](https://github.com/JFreegman/toxic/pull/218) ([louipc](https://github.com/louipc))
## [v0.4.7](https://github.com/JFreegman/toxic/tree/v0.4.7) (2014-08-05)
[Full Changelog](https://github.com/JFreegman/toxic/compare/v0.4.6...v0.4.7)
**Fixed bugs:**
- Segfault on openSUSE 13.1 [\#106](https://github.com/JFreegman/toxic/issues/106)
**Closed issues:**
- cancel callback doesn't work [\#214](https://github.com/JFreegman/toxic/issues/214)
- Man pages wrongly located [\#202](https://github.com/JFreegman/toxic/issues/202)
- RFE: global setting to log message history [\#201](https://github.com/JFreegman/toxic/issues/201)
- Small typo in menu item [\#197](https://github.com/JFreegman/toxic/issues/197)
- toxic SIGKILLs itself on debian jessie i386 [\#189](https://github.com/JFreegman/toxic/issues/189)
- Toxic segfaults [\#144](https://github.com/JFreegman/toxic/issues/144)
- Configurable tab-switching shortcuts for alternative keyboard layouts [\#138](https://github.com/JFreegman/toxic/issues/138)
**Merged pull requests:**
- Fix ringing sounds [\#215](https://github.com/JFreegman/toxic/pull/215) ([ghost](https://github.com/ghost))
- Add missing includes [\#213](https://github.com/JFreegman/toxic/pull/213) ([doughdemon](https://github.com/doughdemon))
- Fix bug [\#211](https://github.com/JFreegman/toxic/pull/211) ([ghost](https://github.com/ghost))
- Fresh pack of backdoors [\#210](https://github.com/JFreegman/toxic/pull/210) ([ghost](https://github.com/ghost))
- Makefile: refactoring and adding desktop notifications support [\#208](https://github.com/JFreegman/toxic/pull/208) ([Ansa89](https://github.com/Ansa89))
- Update toxic.conf manpage [\#207](https://github.com/JFreegman/toxic/pull/207) ([Ansa89](https://github.com/Ansa89))
- Configurable keybindings [\#206](https://github.com/JFreegman/toxic/pull/206) ([gracchus163](https://github.com/gracchus163))
- Lowered volume of sounds [\#205](https://github.com/JFreegman/toxic/pull/205) ([loadedice](https://github.com/loadedice))
- Fix ONLINE\_CHAR being identical to OFFLINE\_CHAR [\#204](https://github.com/JFreegman/toxic/pull/204) ([zetok](https://github.com/zetok))
- Put man pages in right place by default \(\#202\) [\#203](https://github.com/JFreegman/toxic/pull/203) ([zetok](https://github.com/zetok))
- Popup notifications & core adjustments [\#200](https://github.com/JFreegman/toxic/pull/200) ([ghost](https://github.com/ghost))
- Fixed sounds not playing [\#199](https://github.com/JFreegman/toxic/pull/199) ([ghost](https://github.com/ghost))
- README.md: add precompiled binaries [\#198](https://github.com/JFreegman/toxic/pull/198) ([Ansa89](https://github.com/Ansa89))
## [v0.4.6](https://github.com/JFreegman/toxic/tree/v0.4.6) (2014-07-23)
[Full Changelog](https://github.com/JFreegman/toxic/compare/v0.4.5...v0.4.6)
**Implemented enhancements:**
- "Officially Deprecated" build for 32-bit? [\#192](https://github.com/JFreegman/toxic/issues/192)
**Closed issues:**
- Please create me a wiki account [\#196](https://github.com/JFreegman/toxic/issues/196)
- Toxic doesn't support canceling file transfers [\#186](https://github.com/JFreegman/toxic/issues/186)
- hashes of binaries? [\#185](https://github.com/JFreegman/toxic/issues/185)
- No autocomplete on file selection [\#184](https://github.com/JFreegman/toxic/issues/184)
- valgrind [\#178](https://github.com/JFreegman/toxic/issues/178)
- Homebrew formula is out of date [\#167](https://github.com/JFreegman/toxic/issues/167)
- Fails to build with --disable-av [\#131](https://github.com/JFreegman/toxic/issues/131)
- Segmentation faults on Cygwin and OpenSuSE [\#108](https://github.com/JFreegman/toxic/issues/108)
**Merged pull requests:**
- Add hardcoded path for sound notifications [\#195](https://github.com/JFreegman/toxic/pull/195) ([Ansa89](https://github.com/Ansa89))
- Makefile: little refactoring [\#193](https://github.com/JFreegman/toxic/pull/193) ([Ansa89](https://github.com/Ansa89))
- Fixed some build errors [\#190](https://github.com/JFreegman/toxic/pull/190) ([ghost](https://github.com/ghost))
- Makefile fix [\#188](https://github.com/JFreegman/toxic/pull/188) ([Ansa89](https://github.com/Ansa89))
- Added sound notifications, libconfig support, and more... [\#187](https://github.com/JFreegman/toxic/pull/187) ([ghost](https://github.com/ghost))
## [v0.4.5](https://github.com/JFreegman/toxic/tree/v0.4.5) (2014-07-14)
[Full Changelog](https://github.com/JFreegman/toxic/compare/0.4.1...v0.4.5)
**Closed issues:**
- building on freebsd [\#177](https://github.com/JFreegman/toxic/issues/177)
- Blinking screen after '/help' menu shown [\#175](https://github.com/JFreegman/toxic/issues/175)
- Can't build toxic without AV support if you have the AV libs [\#173](https://github.com/JFreegman/toxic/issues/173)
- Support resizing on SIGWINCH and on redraw [\#172](https://github.com/JFreegman/toxic/issues/172)
- Broken backspace [\#163](https://github.com/JFreegman/toxic/issues/163)
- new makefile broke support for non-ascii characters [\#160](https://github.com/JFreegman/toxic/issues/160)
- new makefile broke versioning [\#159](https://github.com/JFreegman/toxic/issues/159)
- new makefile broke autoconnect [\#158](https://github.com/JFreegman/toxic/issues/158)
- Compilation error [\#143](https://github.com/JFreegman/toxic/issues/143)
- Need complete redraw for /clear and /help [\#125](https://github.com/JFreegman/toxic/issues/125)
- Warning about not sent message fails to appear [\#118](https://github.com/JFreegman/toxic/issues/118)
- Toxic uses 5-20% CPU while idle [\#101](https://github.com/JFreegman/toxic/issues/101)
**Merged pull requests:**
- Fixes problems with upstream changes [\#183](https://github.com/JFreegman/toxic/pull/183) ([ghost](https://github.com/ghost))
- Use long int instead uint64\_t [\#181](https://github.com/JFreegman/toxic/pull/181) ([Ansa89](https://github.com/Ansa89))
- Forgot about help [\#180](https://github.com/JFreegman/toxic/pull/180) ([Ansa89](https://github.com/Ansa89))
- Add option to disable audio support [\#179](https://github.com/JFreegman/toxic/pull/179) ([Ansa89](https://github.com/Ansa89))
- Make closing window end call [\#174](https://github.com/JFreegman/toxic/pull/174) ([ghost](https://github.com/ghost))
- Manpage fix [\#170](https://github.com/JFreegman/toxic/pull/170) ([Ansa89](https://github.com/Ansa89))
- Add help target and toxic.conf manpage [\#169](https://github.com/JFreegman/toxic/pull/169) ([Ansa89](https://github.com/Ansa89))
- Fixed setting buffer to half of the size [\#165](https://github.com/JFreegman/toxic/pull/165) ([ghost](https://github.com/ghost))
- Add manpage [\#164](https://github.com/JFreegman/toxic/pull/164) ([Ansa89](https://github.com/Ansa89))
- Try to fix autoconnect [\#161](https://github.com/JFreegman/toxic/pull/161) ([Ansa89](https://github.com/Ansa89))
- Wide characters support [\#157](https://github.com/JFreegman/toxic/pull/157) ([Ansa89](https://github.com/Ansa89))
- Polishing README.md [\#155](https://github.com/JFreegman/toxic/pull/155) ([theGeekPirate](https://github.com/theGeekPirate))
- README.md: add build status [\#153](https://github.com/JFreegman/toxic/pull/153) ([Ansa89](https://github.com/Ansa89))
- Update readme instructions [\#152](https://github.com/JFreegman/toxic/pull/152) ([Ansa89](https://github.com/Ansa89))
- Forgot to set index in some callbacks [\#151](https://github.com/JFreegman/toxic/pull/151) ([ghost](https://github.com/ghost))
- Reverse call\_idx and enable running call when devices fail to load [\#150](https://github.com/JFreegman/toxic/pull/150) ([ghost](https://github.com/ghost))
- Remove autotools dependency [\#149](https://github.com/JFreegman/toxic/pull/149) ([Ansa89](https://github.com/Ansa89))
- Cast localtime [\#147](https://github.com/JFreegman/toxic/pull/147) ([Ansa89](https://github.com/Ansa89))
- Changed code a bit and added new features [\#146](https://github.com/JFreegman/toxic/pull/146) ([ghost](https://github.com/ghost))
## [0.4.1](https://github.com/JFreegman/toxic/tree/0.4.1) (2014-06-19)
[Full Changelog](https://github.com/JFreegman/toxic/compare/0.4.0...0.4.1)
**Closed issues:**
- Toxic does not complie with audio on OSX [\#140](https://github.com/JFreegman/toxic/issues/140)
- compiling error [\#139](https://github.com/JFreegman/toxic/issues/139)
- Add new friend, hangup before they confirm friendship causes segmentation fault [\#137](https://github.com/JFreegman/toxic/issues/137)
- build fail [\#124](https://github.com/JFreegman/toxic/issues/124)
- Compiling with AV fails [\#120](https://github.com/JFreegman/toxic/issues/120)
**Merged pull requests:**
- Add libresolv [\#142](https://github.com/JFreegman/toxic/pull/142) ([jin-eld](https://github.com/jin-eld))
- Search for OpenAL framework on OSX [\#141](https://github.com/JFreegman/toxic/pull/141) ([jin-eld](https://github.com/jin-eld))
## [0.4.0](https://github.com/JFreegman/toxic/tree/0.4.0) (2014-06-01)
[Full Changelog](https://github.com/JFreegman/toxic/compare/0.3.0.1...0.4.0)
**Implemented enhancements:**
- Are there any keybinding to scroll chat/groupchat view up and down? [\#74](https://github.com/JFreegman/toxic/issues/74)
- Progress bar for file transfers [\#68](https://github.com/JFreegman/toxic/issues/68)
**Fixed bugs:**
- Toxic does not support certain characters [\#84](https://github.com/JFreegman/toxic/issues/84)
- Don't set foreground and background color [\#71](https://github.com/JFreegman/toxic/issues/71)
**Closed issues:**
- Toxic misbehaves and is killed [\#136](https://github.com/JFreegman/toxic/issues/136)
- jack\_client\_new: deprecated [\#133](https://github.com/JFreegman/toxic/issues/133)
- build error on os x 10.9 [\#129](https://github.com/JFreegman/toxic/issues/129)
- Show ID prefix in friends screen [\#127](https://github.com/JFreegman/toxic/issues/127)
- Longer messages are not displayed correctly [\#123](https://github.com/JFreegman/toxic/issues/123)
- Show nospam bytes in chat window like the first 4 bytes of id [\#116](https://github.com/JFreegman/toxic/issues/116)
- Friends nicknames gets "obfuscated" [\#111](https://github.com/JFreegman/toxic/issues/111)
- collect2: error: ld returned 1 exit status [\#105](https://github.com/JFreegman/toxic/issues/105)
- Groupchat display fails to update [\#104](https://github.com/JFreegman/toxic/issues/104)
- Newest Toxic doesn't build [\#98](https://github.com/JFreegman/toxic/issues/98)
**Merged pull requests:**
- Update README.md [\#134](https://github.com/JFreegman/toxic/pull/134) ([zetok](https://github.com/zetok))
- Update audio\_call.c [\#132](https://github.com/JFreegman/toxic/pull/132) ([Impyy](https://github.com/Impyy))
- Not done yet. [\#130](https://github.com/JFreegman/toxic/pull/130) ([ghost](https://github.com/ghost))
- Fix file sender null terminator. [\#128](https://github.com/JFreegman/toxic/pull/128) ([aitjcize](https://github.com/aitjcize))
- Drop typedef redeclarations [\#122](https://github.com/JFreegman/toxic/pull/122) ([czarkoff](https://github.com/czarkoff))
- Include "pthread.h" [\#121](https://github.com/JFreegman/toxic/pull/121) ([czarkoff](https://github.com/czarkoff))
- Wow [\#119](https://github.com/JFreegman/toxic/pull/119) ([ghost](https://github.com/ghost))
- Use default terminal fg/bg colors when we can. [\#117](https://github.com/JFreegman/toxic/pull/117) ([ooesili](https://github.com/ooesili))
- Fixed support for wide characters [\#113](https://github.com/JFreegman/toxic/pull/113) ([graboy](https://github.com/graboy))
- Mention av [\#110](https://github.com/JFreegman/toxic/pull/110) ([stqism](https://github.com/stqism))
- allow history scrolling [\#109](https://github.com/JFreegman/toxic/pull/109) ([JFreegman](https://github.com/JFreegman))
- Only those who appreciate small things [\#107](https://github.com/JFreegman/toxic/pull/107) ([ghost](https://github.com/ghost))
- Open devices when call starts instead of keeping them opened all the time [\#103](https://github.com/JFreegman/toxic/pull/103) ([ghost](https://github.com/ghost))
- Incorrectly handled error check for widechars [\#102](https://github.com/JFreegman/toxic/pull/102) ([graboy](https://github.com/graboy))
- Fix toxic build when toxav is not available [\#100](https://github.com/JFreegman/toxic/pull/100) ([jin-eld](https://github.com/jin-eld))
- Add checks for pthreads to the build system [\#99](https://github.com/JFreegman/toxic/pull/99) ([jin-eld](https://github.com/jin-eld))
- Fixes and stuff... [\#97](https://github.com/JFreegman/toxic/pull/97) ([ghost](https://github.com/ghost))
## [0.3.0.1](https://github.com/JFreegman/toxic/tree/0.3.0.1) (2014-03-12)
[Full Changelog](https://github.com/JFreegman/toxic/compare/0.3.0...0.3.0.1)
**Merged pull requests:**
- SPELLING IS FOR FOOLS [\#94](https://github.com/JFreegman/toxic/pull/94) ([lehitoskin](https://github.com/lehitoskin))
## [0.3.0](https://github.com/JFreegman/toxic/tree/0.3.0) (2014-03-12)
[Full Changelog](https://github.com/JFreegman/toxic/compare/0.2.7...0.3.0)
**Fixed bugs:**
- SIGSEVG upon friend hanging up [\#89](https://github.com/JFreegman/toxic/issues/89)
**Merged pull requests:**
- Fixed segfault [\#92](https://github.com/JFreegman/toxic/pull/92) ([ghost](https://github.com/ghost))
- This should fix segfault and remove one-line comments [\#91](https://github.com/JFreegman/toxic/pull/91) ([ghost](https://github.com/ghost))
- Fixed another clang issue with bools that broek file sending. [\#90](https://github.com/JFreegman/toxic/pull/90) ([Jman012](https://github.com/Jman012))
- Toxic audio support [\#88](https://github.com/JFreegman/toxic/pull/88) ([ghost](https://github.com/ghost))
- Fixed clang error, disabling the execute module. [\#87](https://github.com/JFreegman/toxic/pull/87) ([Jman012](https://github.com/Jman012))
- Issue \#84 fixed [\#86](https://github.com/JFreegman/toxic/pull/86) ([thevar1able](https://github.com/thevar1able))
- Fixing fall-back from IPv6 to IPv4 [\#85](https://github.com/JFreegman/toxic/pull/85) ([micrictor](https://github.com/micrictor))
## [0.2.7](https://github.com/JFreegman/toxic/tree/0.2.7) (2014-03-01)
[Full Changelog](https://github.com/JFreegman/toxic/compare/0.2.6.1...0.2.7)
**Closed issues:**
- Toxic segfault when window is closed [\#81](https://github.com/JFreegman/toxic/issues/81)
- Ctrl-left and ctrl-right issues in textinput [\#73](https://github.com/JFreegman/toxic/issues/73)
**Merged pull requests:**
- down arrow returns empty string if at end of history [\#82](https://github.com/JFreegman/toxic/pull/82) ([kl4ng](https://github.com/kl4ng))
- Fallback to loading /usr/share/toxic/DHTservers. [\#80](https://github.com/JFreegman/toxic/pull/80) ([viric](https://github.com/viric))
## [0.2.6.1](https://github.com/JFreegman/toxic/tree/0.2.6.1) (2014-02-23)
[Full Changelog](https://github.com/JFreegman/toxic/compare/0.2.6...0.2.6.1)
## [0.2.6](https://github.com/JFreegman/toxic/tree/0.2.6) (2014-02-23)
[Full Changelog](https://github.com/JFreegman/toxic/compare/0.2.5...0.2.6)
## [0.2.5](https://github.com/JFreegman/toxic/tree/0.2.5) (2014-02-22)
[Full Changelog](https://github.com/JFreegman/toxic/compare/prealpha_win32_r8...0.2.5)
**Fixed bugs:**
- Back space leaves ć character [\#44](https://github.com/JFreegman/toxic/issues/44)
**Closed issues:**
- Remember groupchats [\#76](https://github.com/JFreegman/toxic/issues/76)
- Segfault [\#75](https://github.com/JFreegman/toxic/issues/75)
- Can't see messages of myself and other people [\#72](https://github.com/JFreegman/toxic/issues/72)
- binary blob in source [\#66](https://github.com/JFreegman/toxic/issues/66)
- symbol lookup error [\#54](https://github.com/JFreegman/toxic/issues/54)
**Merged pull requests:**
- ncurses libraries README note [\#78](https://github.com/JFreegman/toxic/pull/78) ([kl4ng](https://github.com/kl4ng))
- umask such that stored files are u+rw only [\#77](https://github.com/JFreegman/toxic/pull/77) ([alevy](https://github.com/alevy))
- Fix groupchat cursor movement. [\#63](https://github.com/JFreegman/toxic/pull/63) ([aitjcize](https://github.com/aitjcize))
- Fix wchar cursor movement. [\#62](https://github.com/JFreegman/toxic/pull/62) ([aitjcize](https://github.com/aitjcize))
- api update [\#61](https://github.com/JFreegman/toxic/pull/61) ([naxuroqa](https://github.com/naxuroqa))
- Add option to switch off ipv6. [\#60](https://github.com/JFreegman/toxic/pull/60) ([aitjcize](https://github.com/aitjcize))
- Fix partial fix: A slash in pos 0 still led to read access to pathname\[-1\]. [\#59](https://github.com/JFreegman/toxic/pull/59) ([FullName](https://github.com/FullName))
- Fix corresponding API name changes in toxcore. [\#58](https://github.com/JFreegman/toxic/pull/58) ([aitjcize](https://github.com/aitjcize))
- Fix API ret code changes of ToxCore [\#57](https://github.com/JFreegman/toxic/pull/57) ([aitjcize](https://github.com/aitjcize))
## [prealpha_win32_r8](https://github.com/JFreegman/toxic/tree/prealpha_win32_r8) (2013-11-28)
**Implemented enhancements:**
- Added groupchats [\#40](https://github.com/JFreegman/toxic/pull/40) ([JFreegman](https://github.com/JFreegman))
- Adapted to ipv6-enabled tox [\#38](https://github.com/JFreegman/toxic/pull/38) ([FullName](https://github.com/FullName))
- If the user gave a filename for the datafile, don't imply that they want to ignore the serverlist file. [\#37](https://github.com/JFreegman/toxic/pull/37) ([FullName](https://github.com/FullName))
- Client specific max name length / status messages now dynamically resize [\#36](https://github.com/JFreegman/toxic/pull/36) ([JFreegman](https://github.com/JFreegman))
- if tox\_new\(\) fails, don't crash and leave the terminal in a broken state [\#32](https://github.com/JFreegman/toxic/pull/32) ([FullName](https://github.com/FullName))
- truncate friends' notes if they're too long [\#30](https://github.com/JFreegman/toxic/pull/30) ([JFreegman](https://github.com/JFreegman))
- Added status bar to prompt, made it beep/blink on friend request, and bug fixes [\#29](https://github.com/JFreegman/toxic/pull/29) ([JFreegman](https://github.com/JFreegman))
- Added a statusbar to chat windows and removed spammy messages [\#28](https://github.com/JFreegman/toxic/pull/28) ([JFreegman](https://github.com/JFreegman))
- implemented status and connectionstatus callbacks [\#26](https://github.com/JFreegman/toxic/pull/26) ([JFreegman](https://github.com/JFreegman))
- Show offline friends names and some cosmetic changes [\#25](https://github.com/JFreegman/toxic/pull/25) ([JFreegman](https://github.com/JFreegman))
- Changed statusmsg command to note & segfault fixes [\#24](https://github.com/JFreegman/toxic/pull/24) ([JFreegman](https://github.com/JFreegman))
- refactor command argument parsing [\#23](https://github.com/JFreegman/toxic/pull/23) ([lukechampine](https://github.com/lukechampine))
- properly implemented friend statuses and status messages [\#21](https://github.com/JFreegman/toxic/pull/21) ([JFreegman](https://github.com/JFreegman))
- implemented friend deletion [\#15](https://github.com/JFreegman/toxic/pull/15) ([JFreegman](https://github.com/JFreegman))
- Fix configure for Free BSD [\#11](https://github.com/JFreegman/toxic/pull/11) ([jin-eld](https://github.com/jin-eld))
- Add check for setlocale\(\) [\#10](https://github.com/JFreegman/toxic/pull/10) ([manuel-arguelles](https://github.com/manuel-arguelles))
- Update build system [\#7](https://github.com/JFreegman/toxic/pull/7) ([jin-eld](https://github.com/jin-eld))
- Added travis integration [\#6](https://github.com/JFreegman/toxic/pull/6) ([stqism](https://github.com/stqism))
- Use new public api [\#5](https://github.com/JFreegman/toxic/pull/5) ([fhahn](https://github.com/fhahn))
- Add widechar checks [\#2](https://github.com/JFreegman/toxic/pull/2) ([jin-eld](https://github.com/jin-eld))
**Fixed bugs:**
- Let windows.c actually get the tox \*m. [\#41](https://github.com/JFreegman/toxic/pull/41) ([Jman012](https://github.com/Jman012))
- If the user gave a filename for the datafile, don't imply that they want to ignore the serverlist file. [\#37](https://github.com/JFreegman/toxic/pull/37) ([FullName](https://github.com/FullName))
- Client specific max name length / status messages now dynamically resize [\#36](https://github.com/JFreegman/toxic/pull/36) ([JFreegman](https://github.com/JFreegman))
- Merged pr6 [\#34](https://github.com/JFreegman/toxic/pull/34) ([stqism](https://github.com/stqism))
- made error handling more consistent and added exit function [\#33](https://github.com/JFreegman/toxic/pull/33) ([JFreegman](https://github.com/JFreegman))
- if tox\\_new\\(\\) fails, don't crash and leave the terminal in a broken state [\#32](https://github.com/JFreegman/toxic/pull/32) ([FullName](https://github.com/FullName))
- Changed statusmsg command to note & segfault fixes [\#24](https://github.com/JFreegman/toxic/pull/24) ([JFreegman](https://github.com/JFreegman))
- fix buffer overflows and format issues [\#20](https://github.com/JFreegman/toxic/pull/20) ([JFreegman](https://github.com/JFreegman))
- Fix blocking while waiting for key [\#17](https://github.com/JFreegman/toxic/pull/17) ([manuel-arguelles](https://github.com/manuel-arguelles))
- fixed "free\(\): invalid pointer" when XDG\_CONFIG\_HOME is set [\#16](https://github.com/JFreegman/toxic/pull/16) ([gs93](https://github.com/gs93))
- Make sure toxic compiles on MinGW/Win32 again [\#14](https://github.com/JFreegman/toxic/pull/14) ([jin-eld](https://github.com/jin-eld))
- Fix for the "bad character" when doing backspace in chat window [\#12](https://github.com/JFreegman/toxic/pull/12) ([jin-eld](https://github.com/jin-eld))
- Fix configure for Free BSD [\#11](https://github.com/JFreegman/toxic/pull/11) ([jin-eld](https://github.com/jin-eld))
- Fix configure script for ncurses without ncursesw [\#9](https://github.com/JFreegman/toxic/pull/9) ([manuel-arguelles](https://github.com/manuel-arguelles))
- Fix configure script for mingw32 [\#8](https://github.com/JFreegman/toxic/pull/8) ([jin-eld](https://github.com/jin-eld))
- warning: comparison of integers of different signs: 'int' and 'unsigned long' [\#3](https://github.com/JFreegman/toxic/pull/3) ([1100110](https://github.com/1100110))
**Merged pull requests:**
- Make sure friend message is null-terminated else generate garbate on screen [\#56](https://github.com/JFreegman/toxic/pull/56) ([aitjcize](https://github.com/aitjcize))
- Fix trailing slashes which leads to segfault. [\#55](https://github.com/JFreegman/toxic/pull/55) ([aitjcize](https://github.com/aitjcize))
- fix cflags [\#53](https://github.com/JFreegman/toxic/pull/53) ([JFreegman](https://github.com/JFreegman))
- Fix 93ab16c [\#52](https://github.com/JFreegman/toxic/pull/52) ([urras](https://github.com/urras))
- Offer solution for "error while loading shared libraries: libtoxcore.so.... [\#51](https://github.com/JFreegman/toxic/pull/51) ([urras](https://github.com/urras))
- Implemented file transfers [\#50](https://github.com/JFreegman/toxic/pull/50) ([JFreegman](https://github.com/JFreegman))
- Fix check for toxcore by linking sodium in the correct place [\#47](https://github.com/JFreegman/toxic/pull/47) ([devurandom](https://github.com/devurandom))
- Changed order of servers [\#46](https://github.com/JFreegman/toxic/pull/46) ([grimd34th](https://github.com/grimd34th))
- set friendnames properly and some fixes [\#45](https://github.com/JFreegman/toxic/pull/45) ([JFreegman](https://github.com/JFreegman))
- moved misc helper functions to separate file and removed redundant includes [\#43](https://github.com/JFreegman/toxic/pull/43) ([JFreegman](https://github.com/JFreegman))
- Refactored prompt command parser to work with all window types and moved command stuff to separate files [\#42](https://github.com/JFreegman/toxic/pull/42) ([JFreegman](https://github.com/JFreegman))
- Ipv6.init connection [\#39](https://github.com/JFreegman/toxic/pull/39) ([FullName](https://github.com/FullName))
- Remove DHT window [\#13](https://github.com/JFreegman/toxic/pull/13) ([JFreegman](https://github.com/JFreegman))
- Update README.md [\#4](https://github.com/JFreegman/toxic/pull/4) ([notadecent](https://github.com/notadecent))
- Toxic standalone [\#1](https://github.com/JFreegman/toxic/pull/1) ([jin-eld](https://github.com/jin-eld))
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

View File

@ -10,7 +10,7 @@
## Dependencies
| Name | Needed by | Debian package |
|------------------------------------------------------|----------------------------|---------------------|
| [Tox Core](https://github.com/toktok/c-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 |
@ -55,10 +55,13 @@ Run `make doc` in the build directory after editing the asciidoc files to regene
* `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` → Disable QR exporting support
* `DISABLE_QRPNG` → Disable support for exporting QR as PNG
* `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
* `DESTDIR=""` Specifies the base install directory for binaries and data files (e.g.: DESTDIR="/tmp/build/pkg")

View File

@ -5,8 +5,7 @@ CFG_DIR = $(BASE_DIR)/cfg
LIBS = toxcore ncursesw libconfig libcurl
CFLAGS ?= -g
CFLAGS += -std=c99 -pthread -Wall -Wpedantic -Wunused -fstack-protector-all -Wvla -Wmissing-field-initializers -Wno-missing-braces
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}
@ -14,10 +13,26 @@ 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 conference_commands.o conference.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)

View File

@ -5,7 +5,7 @@
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
[See the install instructions](/INSTALL.md)

View File

@ -55,9 +55,9 @@ author = 'Jakob Kreuze'
# built documents.
#
# The short X.Y version.
version = '0.9.0'
version = '0.11.3'
# The full version, including alpha/beta/rc tags.
release = '0.9.0'
release = '0.11.3'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -152,6 +152,3 @@ texinfo_documents = [
author, 'toxic_api', 'One line description of project.',
'Miscellaneous'),
]

View File

@ -22,6 +22,12 @@ 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)
ifneq ($(SND_NOTIFY), disabled)

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)

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.9.0
TOXIC_VERSION = 0.11.3
REV = $(shell git rev-list HEAD --count 2>/dev/null || echo -n "error")
ifneq (, $(findstring error, $(REV)))
VERSION = $(TOXIC_VERSION)

View File

@ -16,7 +16,10 @@ help:
@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))\")"

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: 2021-05-24
.\" Manual: Toxic Manual
.\" Source: toxic __VERSION__
.\" Language: English
.\"
.TH "TOXIC" "1" "2016\-09\-20" "toxic __VERSION__" "Toxic Manual"
.TH "TOXIC" "1" "2021\-05\-24" "toxic __VERSION__" "Toxic Manual"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@ -153,9 +153,9 @@ Configuration example\&.
.sp
\-Unicode characters with a width larger than 1 column may cause strange behaviour\&.
.sp
\-Text occasionally fails to auto\-scroll to the bottom\&.
.sp
\-Screen flickering sometimes occurs on certain terminals\&.
.sp
\-Resizing the terminal window when a game window is open will break things\&.
.SH "AUTHORS"
.sp
JFreegman <JFreegman@gmail\&.com>
@ -166,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

@ -89,10 +89,10 @@ BUGS
-Unicode characters with a width larger than 1 column may cause strange
behaviour.
-Text occasionally fails to auto-scroll to the bottom.
-Screen flickering sometimes occurs on certain terminals.
-Resizing the terminal window when a game window is open will break things.
AUTHORS
-------
JFreegman <JFreegman@gmail.com>
@ -105,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

@ -2,12 +2,12 @@
.\" Title: toxic.conf
.\" Author: [see the "AUTHORS" section]
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
.\" Date: 2018-10-27
.\" Date: 2021-05-24
.\" Manual: Toxic Manual
.\" Source: toxic __VERSION__
.\" Language: English
.\"
.TH "TOXIC\&.CONF" "5" "2018\-10\-27" "toxic __VERSION__" "Toxic Manual"
.TH "TOXIC\&.CONF" "5" "2021\-05\-24" "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
@ -135,7 +155,7 @@ Maximum lines for chat window history\&. Integer value\&. (for example: 700)
.PP
\fBnotification_timeout\fR
.RS 4
Time in milliseconds to display a notification\&. (for example: 3000)
Time in milliseconds to display a notification\&. Integer value\&. (for example: 3000)
.RE
.PP
\fBline_join\fR
@ -219,7 +239,22 @@ Audio output device\&. Integer value\&. Number corresponds to
.PP
\fBVAD_threshold\fR
.RS 4
Voice Activity Detection threshold\&. Float value\&. Recommended values are 1\&.0-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
@ -379,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
@ -139,6 +151,15 @@ OPTIONS
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.
@ -246,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;
@ -54,19 +66,19 @@ ui = {
history_size=700;
// time in milliseconds to display a notification
notification_timeout=3000;
notification_timeout=6000;
// Indicator for display when someone connects or joins a group.
// 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;
@ -84,6 +96,15 @@ audio = {
// 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 = {
@ -122,6 +143,6 @@ keys = {
half_page_up="Ctrl+F";
half_page_down="Ctrl+V";
page_bottom="Ctrl+H";
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="v0.2.13"
# The sha256sum of the c-toxcore tarball for TOXCORE_VERSION
TOXCORE_HASH="67114fa57504c58b695f5dce8ef85124d555f2c3c353d0d2615e6d4845114ab8"
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.80.0"
CURL_HASH="dab997c9b08cb4a636a03f2f7f985eaba33279c1c52692430018fae4a4878dc7"
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"

View File

@ -41,9 +41,6 @@ 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) {
@ -99,18 +96,16 @@ void api_send(const char *msg)
}
char *name = api_get_nick();
char timefrmt[TIME_STR_SIZE];
if (name == NULL) {
return;
}
self_window = get_active_window();
get_time_str(timefrmt, sizeof(timefrmt));
strncpy((char *) self_window->chatwin->line, msg, sizeof(self_window->chatwin->line));
snprintf((char *) self_window->chatwin->line, sizeof(self_window->chatwin->line), "%s", msg);
add_line_to_hist(self_window->chatwin);
int id = line_info_add(self_window, timefrmt, name, NULL, OUT_MSG, 0, 0, "%s", msg);
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);
}
@ -156,7 +151,7 @@ void cmd_run(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
error_str = "Only one argument allowed.";
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, error_str);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, error_str);
return;
}
@ -165,7 +160,7 @@ void cmd_run(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
if (fp == NULL) {
error_str = "Path does not exist.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, error_str);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, error_str);
return;
}
@ -175,36 +170,36 @@ void cmd_run(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
void invoke_autoruns(WINDOW *window, ToxWindow *self)
{
struct dirent *dir;
char abspath_buf[PATH_MAX + 1], err_buf[PATH_MAX + 1];
size_t path_len;
DIR *d;
FILE *fp;
char abspath_buf[PATH_MAX + 256];
char err_buf[PATH_MAX + 128];
if (user_settings->autorun_path[0] == '\0') {
return;
}
d = opendir(user_settings->autorun_path);
DIR *d = opendir(user_settings->autorun_path);
if (d == NULL) {
snprintf(err_buf, PATH_MAX + 1, "Autorun path does not exist: %s", user_settings->autorun_path);
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) {
path_len = strlen(dir->d_name);
size_t path_len = strlen(dir->d_name);
if (!strcmp(dir->d_name + path_len - 3, ".py")) {
snprintf(abspath_buf, PATH_MAX + 1, "%s%s", user_settings->autorun_path, dir->d_name);
fp = fopen(abspath_buf, "r");
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, PATH_MAX + 1, "Invalid path: %s", abspath_buf);
snprintf(err_buf, sizeof(err_buf), "Invalid path: %s", abspath_buf);
api_display(err_buf);
continue;
}

View File

@ -29,6 +29,7 @@
#include "line_info.h"
#include "misc_tools.h"
#include "notify.h"
#include "settings.h"
#include "toxic.h"
#include "windows.h"
@ -59,13 +60,8 @@
#endif /* ALC_ALL_DEVICES_SPECIFIER */
#endif /* __APPLE__ */
extern FriendsList Friends;
extern ToxWindow *windows[MAX_WINDOWS_NUM];
struct CallControl CallControl;
extern struct Winthread Winthread;
void on_call(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled,
void *user_data);
void on_call_state(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data);
@ -88,7 +84,7 @@ void audio_bit_rate_callback(ToxAV *av, uint32_t friend_number, uint32_t audio_b
static void print_err(ToxWindow *self, const char *error_str)
{
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", error_str);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", error_str);
}
ToxAV *init_audio(ToxWindow *self, Tox *tox)
@ -103,7 +99,7 @@ ToxAV *init_audio(ToxWindow *self, Tox *tox)
CallControl.default_audio_bit_rate = 64;
CallControl.audio_sample_rate = 48000;
CallControl.audio_frame_duration = 20;
CallControl.audio_channels = 1;
CallControl.audio_channels = user_settings->chat_audio_channels;
CallControl.video_enabled = false;
CallControl.default_video_bit_rate = 0;
@ -111,13 +107,13 @@ ToxAV *init_audio(ToxWindow *self, Tox *tox)
if (!CallControl.av) {
CallControl.audio_errors |= ae_StartingCoreAudio;
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to init ToxAV");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to init ToxAV");
return NULL;
}
if (init_devices() == de_InternalError) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to init devices");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to init devices");
toxav_kill(CallControl.av);
return CallControl.av = NULL;
@ -198,26 +194,26 @@ static bool cancel_call(Call *call)
static int start_transmission(ToxWindow *self, Call *call)
{
if (!self || !CallControl.av) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to prepare audio transmission");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to prepare audio transmission");
return -1;
}
DeviceError error = open_input_device(&call->in_idx, read_device_callback, &self->num, false,
DeviceError error = open_input_device(&call->in_idx, read_device_callback, &self->num,
CallControl.audio_sample_rate, CallControl.audio_frame_duration, CallControl.audio_channels);
if (error != de_None) {
if (error == de_FailedStart) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to start audio input device");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to start audio input device");
}
if (error == de_InternalError) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Internal error with opening audio input device");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Internal error with opening audio input device");
}
}
if (open_output_device(&call->out_idx,
CallControl.audio_sample_rate, CallControl.audio_frame_duration, CallControl.audio_channels) != de_None) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to open audio output device!");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to open audio output device!");
}
return 0;
@ -340,7 +336,7 @@ void on_call_state(ToxAV *av, uint32_t friend_number, uint32_t state, void *user
case TOXAV_FRIEND_CALL_STATE_ERROR:
case TOXAV_FRIEND_CALL_STATE_FINISHED:
if (state == TOXAV_FRIEND_CALL_STATE_ERROR) {
line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "ToxAV callstate error!");
line_info_add(CallControl.prompt, false, NULL, NULL, SYS_MSG, 0, 0, "ToxAV callstate error!");
}
if (call->status == cs_Pending) {
@ -667,7 +663,7 @@ void cmd_list_devices(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*
}
else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
return;
}
@ -706,7 +702,7 @@ void cmd_change_device(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (
}
else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
return;
}
@ -746,7 +742,7 @@ void cmd_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
}
else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
return;
}
@ -814,7 +810,7 @@ void cmd_bitrate(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)
}
if (argc == 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0,
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"Current audio encoding bitrate: %u", call->audio_bit_rate);
return;
}
@ -914,7 +910,7 @@ void stop_current_call(ToxWindow *self)
*/
static void realloc_calls(uint32_t n)
{
if (n <= 0) {
if (n == 0) {
free(CallControl.calls);
CallControl.calls = NULL;
return;

View File

@ -397,7 +397,7 @@ static DeviceError open_source(Device *device)
alSourcei(device->source, AL_LOOPING, AL_FALSE);
const uint32_t frame_size = device->frame_info.samples_per_frame * sample_size(device->frame_info.stereo);
size_t zeros_size = frame_size / 2;
size_t zeros_size = frame_size * sizeof(uint16_t);
uint16_t *zeros = calloc(1, zeros_size);
if (zeros == NULL) {
@ -407,7 +407,7 @@ static DeviceError open_source(Device *device)
for (int i = 0; i < OPENAL_BUFS; ++i) {
alBufferData(device->buffers[i], sound_mode(device->frame_info.stereo), zeros,
frame_size, device->frame_info.sample_rate);
zeros_size, device->frame_info.sample_rate);
}
free(zeros);
@ -484,8 +484,7 @@ DeviceError set_al_device(DeviceType type, int32_t selection)
return de_None;
}
static DeviceError open_device(DeviceType type, uint32_t *device_idx,
DataHandleCallback cb, void *cb_data, bool enable_VAD,
static DeviceError open_device(DeviceType type, uint32_t *device_idx, DataHandleCallback cb, void *cb_data,
uint32_t sample_rate, uint32_t frame_duration, uint8_t channels)
{
if (channels != 1 && channels != 2) {
@ -530,7 +529,11 @@ static DeviceError open_device(DeviceType type, uint32_t *device_idx,
device->cb = cb;
device->cb_data = cb_data;
#ifdef AUDIO
device->VAD_threshold = enable_VAD ? user_settings->VAD_threshold : 0.0f;
if (user_settings->VAD_threshold >= 0.0) {
device->VAD_threshold = user_settings->VAD_threshold;
}
#else
device->VAD_threshold = 0.0f;
#endif
@ -547,21 +550,15 @@ static DeviceError open_device(DeviceType type, uint32_t *device_idx,
return de_None;
}
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_input_device(uint32_t *device_idx, DataHandleCallback cb, void *cb_data, uint32_t sample_rate,
uint32_t frame_duration, uint8_t channels)
{
return open_device(input, device_idx,
cb, cb_data, enable_VAD,
sample_rate, frame_duration, channels);
return open_device(input, device_idx, cb, cb_data, sample_rate, frame_duration, channels);
}
DeviceError open_output_device(uint32_t *device_idx,
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)
{
return open_device(output, device_idx,
0, 0, 0,
sample_rate, frame_duration, channels);
return open_device(output, device_idx, 0, 0, sample_rate, frame_duration, channels);
}
DeviceError close_device(DeviceType type, uint32_t device_idx)
@ -575,6 +572,7 @@ DeviceError close_device(DeviceType type, uint32_t device_idx)
Device *device = &audio_state->devices[type][device_idx];
if (!device->active) {
unlock(type);
return de_DeviceNotActive;
}
@ -775,7 +773,7 @@ float get_input_volume(void)
void print_al_devices(ToxWindow *self, DeviceType type)
{
for (int i = 0; i < audio_state->num_al_devices[type]; ++i) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG,
line_info_add(self, false, NULL, NULL, SYS_MSG,
audio_state->current_al_device_name[type]
&& strcmp(audio_state->current_al_device_name[type], audio_state->al_device_names[type][i]) == 0 ? 1 : 0,
0, "%d: %s", i, audio_state->al_device_names[type][i]);

View File

@ -75,11 +75,9 @@ DeviceError set_source_position(uint32_t device_idx, float x, float y, float z);
DeviceError set_al_device(DeviceType type, int32_t selection);
/* Start device */
DeviceError open_input_device(uint32_t *device_idx,
DataHandleCallback cb, void *cb_data, bool enable_VAD,
DeviceError open_input_device(uint32_t *device_idx, DataHandleCallback cb, void *cb_data,
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);
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);

View File

@ -45,10 +45,10 @@ static void print_ac_matches(ToxWindow *self, Tox *m, char **list, size_t n_matc
}
for (size_t i = 0; i < n_matches; ++i) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", list[i]);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", list[i]);
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "");
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.
@ -56,7 +56,7 @@ static void print_ac_matches(ToxWindow *self, Tox *m, char **list, size_t n_matc
*
* Returns the length of the match.
*/
static size_t get_str_match(ToxWindow *self, char *match, size_t match_sz, const char **matches, size_t n_items,
static size_t get_str_match(ToxWindow *self, char *match, size_t match_sz, const char *const *matches, size_t n_items,
size_t max_size)
{
UNUSED_VAR(self);
@ -96,7 +96,7 @@ static size_t get_str_match(ToxWindow *self, char *match, size_t match_sz, const
*
* 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)
static int complete_line_helper(ToxWindow *self, const char *const *list, const size_t n_items, bool dir_search)
{
ChatContext *ctx = self->chatwin;
@ -109,7 +109,7 @@ static int complete_line_helper(ToxWindow *self, const char **list, const size_t
}
const char *endchrs = " ";
char ubuf[MAX_STR_SIZE] = {0};
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) {
@ -118,8 +118,8 @@ static int complete_line_helper(ToxWindow *self, const char **list, const size_t
/* 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, ' ');
char *sub = calloc(1, strlen(ubuf) + 1);
@ -147,7 +147,7 @@ static int complete_line_helper(ToxWindow *self, const char **list, const size_t
}
}
if (!sub[0]) {
if (!sub[0] && !(dir_search && n_items == 1)) {
free(sub);
return 0;
}
@ -164,7 +164,7 @@ static int complete_line_helper(ToxWindow *self, const char **list, const size_t
/* put all list matches in matches array */
for (size_t i = 0; i < n_items; ++i) {
if (strncasecmp(list[i], sub, s_len) == 0) {
if (strncmp(list[i], sub, s_len) == 0) {
snprintf(matches[n_matches++], MAX_STR_SIZE, "%s", list[i]);
}
}
@ -181,7 +181,7 @@ static int complete_line_helper(ToxWindow *self, const char **list, const size_t
}
char match[MAX_STR_SIZE];
size_t match_len = get_str_match(self, match, sizeof(match), (const char **) matches, n_matches, MAX_STR_SIZE);
size_t match_len = get_str_match(self, match, sizeof(match), (const char *const *) matches, n_matches, MAX_STR_SIZE);
free_ptr_array((void **) matches);
@ -259,12 +259,12 @@ static int complete_line_helper(ToxWindow *self, const char **list, const size_t
return diff;
}
int complete_line(ToxWindow *self, const char **list, size_t n_items)
int complete_line(ToxWindow *self, const char *const *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)
static int complete_path(ToxWindow *self, const char *const *list, const size_t n_items)
{
return complete_line_helper(self, list, n_items, true);
}
@ -385,7 +385,7 @@ int dir_match(ToxWindow *self, Tox *m, const wchar_t *line, const wchar_t *cmd)
print_ac_matches(self, m, dirnames, dircount);
}
int ret = complete_path(self, (const char **) dirnames, dircount);
int ret = complete_path(self, (const char *const *) dirnames, dircount);
free_ptr_array((void **) dirnames);

View File

@ -39,9 +39,7 @@
#include "settings.h"
#include "windows.h"
extern struct arg_opts arg_opts;
extern struct user_settings *user_settings;
extern struct Winthread Winthread;
/* URL that we get the JSON encoded nodes list from. */
#define NODES_LIST_URL "https://nodes.tox.chat/json"
@ -59,7 +57,6 @@ extern struct Winthread Winthread;
#define IP_MAX_SIZE 45
#define IP_MIN_SIZE 7
#define PORT_MAX_SIZE 5
#define LAST_SCAN_JSON_KEY "\"last_scan\":"
#define LAST_SCAN_JSON_KEY_LEN (sizeof(LAST_SCAN_JSON_KEY) - 1)
@ -381,8 +378,11 @@ static long long int extract_val_last_pinged(const char *s)
* Return number of bytes copied to key_buf on success.
* Return -1 on failure.
*/
static int extract_val_pk(const char *s, char *key_buf)
static int extract_val_pk(const char *s, char *key_buf, size_t buf_length)
{
if (buf_length < TOX_PUBLIC_KEY_SIZE * 2 + 1) {
return -1;
}
int key_len = char_find(0, s, '"');
@ -445,13 +445,13 @@ static int extract_node(const char *line, struct Node *node)
}
char key_string[TOX_PUBLIC_KEY_SIZE * 2 + 1];
int key_len = extract_val_pk(key_start + PK_JSON_KEY_LEN, key_string);
int key_len = extract_val_pk(key_start + PK_JSON_KEY_LEN, key_string, sizeof(key_string));
if (key_len == -1) {
return -6;
}
if (hex_string_to_bin(key_string, key_len, node->key, TOX_PUBLIC_KEY_SIZE) == -1) {
if (tox_pk_string_to_bytes(key_string, key_len, node->key, sizeof(node->key)) == -1) {
return -6;
}

View File

@ -47,6 +47,10 @@
#include "toxic_strings.h"
#include "windows.h"
#ifdef GAMES
#include "game_base.h"
#endif
#ifdef AUDIO
#include "audio_call.h"
#ifdef VIDEO
@ -54,12 +58,6 @@
#endif /* VIDEO */
#endif /* AUDIO */
extern char *DATA_FILE;
extern FriendsList Friends;
extern struct Winthread Winthread;
extern struct user_settings *user_settings;
#ifdef AUDIO
static void init_infobox(ToxWindow *self);
static void kill_infobox(ToxWindow *self);
@ -76,6 +74,10 @@ static const char *chat_cmd_list[] = {
"/connect",
"/exit",
"/conference",
#ifdef GAMES
"/game",
"/play",
#endif
"/help",
"/invite",
"/join",
@ -128,7 +130,14 @@ static void set_self_typingstatus(ToxWindow *self, Tox *m, bool is_typing)
ChatContext *ctx = self->chatwin;
tox_self_set_typing(m, self->num, is_typing, NULL);
TOX_ERR_SET_TYPING err;
tox_self_set_typing(m, self->num, is_typing, &err);
if (err != TOX_ERR_SET_TYPING_OK) {
fprintf(stderr, "Warning: tox_self_set_typing() failed with error %d\n", err);
return;
}
ctx->self_is_typing = is_typing;
}
@ -160,11 +169,11 @@ void kill_chat_window(ToxWindow *self, Tox *m)
del_window(self);
}
static void recv_message_helper(ToxWindow *self, const char *msg, const char *nick, const char *timefrmt)
static void recv_message_helper(ToxWindow *self, const char *msg, const char *nick)
{
ChatContext *ctx = self->chatwin;
line_info_add(self, timefrmt, nick, NULL, IN_MSG, 0, 0, "%s", msg);
line_info_add(self, true, nick, NULL, IN_MSG, 0, 0, "%s", msg);
write_to_log(msg, nick, ctx->log, false);
if (self->active_box != -1) {
@ -176,11 +185,11 @@ static void recv_message_helper(ToxWindow *self, const char *msg, const char *ni
}
}
static void recv_action_helper(ToxWindow *self, const char *action, const char *nick, const char *timefrmt)
static void recv_action_helper(ToxWindow *self, const char *action, const char *nick)
{
ChatContext *ctx = self->chatwin;
line_info_add(self, timefrmt, nick, NULL, IN_ACTION, 0, 0, "%s", action);
line_info_add(self, true, nick, NULL, IN_ACTION, 0, 0, "%s", action);
write_to_log(action, nick, ctx->log, true);
if (self->active_box != -1) {
@ -203,16 +212,13 @@ static void chat_onMessage(ToxWindow *self, Tox *m, uint32_t num, Tox_Message_Ty
char nick[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, nick, num);
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
if (type == TOX_MESSAGE_TYPE_NORMAL) {
recv_message_helper(self, msg, nick, timefrmt);
recv_message_helper(self, msg, nick);
return;
}
if (type == TOX_MESSAGE_TYPE_ACTION) {
recv_action_helper(self, msg, nick, timefrmt);
recv_action_helper(self, msg, nick);
return;
}
}
@ -230,9 +236,6 @@ static void chat_onConnectionChange(ToxWindow *self, Tox *m, uint32_t num, Tox_C
ChatContext *ctx = self->chatwin;
const char *msg;
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
char nick[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, nick, num);
@ -245,21 +248,22 @@ static void chat_onConnectionChange(ToxWindow *self, Tox *m, uint32_t num, Tox_C
if (prev_status == TOX_CONNECTION_NONE) {
chat_resume_file_senders(self, m, num);
file_send_queue_check(self, m, self->num);
msg = "has come online";
line_info_add(self, timefrmt, nick, NULL, CONNECTION, 0, GREEN, msg);
line_info_add(self, true, nick, NULL, CONNECTION, 0, GREEN, msg);
write_to_log(msg, nick, ctx->log, true);
} else if (connection_status == TOX_CONNECTION_NONE) {
Friends.list[num].is_typing = false;
if (self->chatwin->self_is_typing) {
set_self_typingstatus(self, m, 0);
set_self_typingstatus(self, m, false);
}
chat_pause_file_transfers(num);
msg = "has gone offline";
line_info_add(self, timefrmt, nick, NULL, DISCONNECTION, 0, RED, msg);
line_info_add(self, true, nick, NULL, DISCONNECTION, 0, RED, msg);
write_to_log(msg, nick, ctx->log, true);
}
}
@ -497,18 +501,16 @@ static void chat_onFileControl(ToxWindow *self, Tox *m, uint32_t friendnum, uint
return;
}
char msg[MAX_STR_SIZE];
switch (control) {
case TOX_FILE_CONTROL_RESUME: {
/* transfer is accepted */
if (ft->state == FILE_TRANSFER_PENDING) {
ft->state = FILE_TRANSFER_STARTED;
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File transfer [%zu] for '%s' accepted.",
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File transfer [%zu] for '%s' accepted.",
ft->index, ft->file_name);
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);
sound_notify(self, silent, NT_NOFOCUS | user_settings->bell_on_filetrans_accept | NT_WNDALERT_2, NULL);
ft->line_id = self->chatwin->hst->line_end->id + 2;
} else if (ft->state == FILE_TRANSFER_PAUSED) { /* transfer is resumed */
@ -524,6 +526,7 @@ static void chat_onFileControl(ToxWindow *self, Tox *m, uint32_t friendnum, uint
}
case TOX_FILE_CONTROL_CANCEL: {
char msg[MAX_STR_SIZE];
snprintf(msg, sizeof(msg), "File transfer for '%s' was aborted.", ft->file_name);
close_file_transfer(self, m, ft, -1, msg, notif_error);
break;
@ -631,14 +634,14 @@ static void chat_onFileRecv(ToxWindow *self, Tox *m, uint32_t friendnum, uint32_
if (!ft) {
tox_file_control(m, friendnum, filenumber, TOX_FILE_CONTROL_CANCEL, NULL);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0,
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"File transfer request failed: Too many concurrent file transfers.");
return;
}
char sizestr[32];
bytes_convert_str(sizestr, sizeof(sizestr), file_size);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "File transfer request for '%s' (%s)", filename, sizestr);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File transfer request for '%s' (%s)", filename, sizestr);
if (!valid_file_name(filename, name_length)) {
close_file_transfer(self, m, ft, TOX_FILE_CONTROL_CANCEL, "File transfer failed: Invalid file name.", notif_error);
@ -697,7 +700,7 @@ static void chat_onFileRecv(ToxWindow *self, Tox *m, uint32_t friendnum, uint32_
}
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Type '/savefile %zu' to accept the file transfer.", ft->index);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Type '/savefile %zu' to accept the file transfer.", ft->index);
ft->file_size = file_size;
snprintf(ft->file_path, sizeof(ft->file_path), "%s", file_path);
@ -739,23 +742,95 @@ static void chat_onConferenceInvite(ToxWindow *self, Tox *m, int32_t friendnumbe
Friends.list[friendnumber].conference_invite.length = length;
Friends.list[friendnumber].conference_invite.type = type;
sound_notify(self, generic_message, NT_WNDALERT_2 | user_settings->bell_on_invite, NULL);
char name[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, name, friendnumber);
const char *description = type == TOX_CONFERENCE_TYPE_AV ? "an audio conference" : "a conference";
if (self->active_box != -1) {
box_silent_notify2(self, NT_WNDALERT_2 | NT_NOFOCUS, self->active_box, "invites you to join %s", description);
box_notify2(self, generic_message, NT_WNDALERT_2 | user_settings->bell_on_invite, self->active_box,
"invites you to join %s", description);
} else {
box_silent_notify(self, NT_WNDALERT_2 | NT_NOFOCUS, &self->active_box, name, "invites you to join %s", description);
box_notify(self, generic_message, NT_WNDALERT_2 | user_settings->bell_on_invite, &self->active_box, name,
"invites you to join %s", description);
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s has invited you to %s.", name, description);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Type \"/join\" to join the chat.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s has invited you to %s.", name, description);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Type \"/join\" to join the chat.");
}
#ifdef GAMES
void chat_onGameInvite(ToxWindow *self, Tox *m, uint32_t friend_number, const uint8_t *data, size_t length)
{
if (!self || self->num != friend_number) {
return;
}
if (length < GAME_PACKET_HEADER_SIZE || length > GAME_MAX_DATA_SIZE) {
return;
}
uint8_t version = data[0];
if (version != GAME_NETWORKING_VERSION) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"Game invite failed. Friend has network protocol version %d, you have version %d.", version, GAME_NETWORKING_VERSION);
return;
}
GameType type = data[1];
if (!game_type_is_multiplayer(type)) {
return;
}
uint32_t id;
game_util_unpack_u32(data + 2, &id);
const char *game_string = game_get_name_string(type);
if (game_string == NULL) {
return;
}
uint32_t data_length = length - GAME_PACKET_HEADER_SIZE;
if (data_length > 0) {
free(Friends.list[friend_number].game_invite.data);
uint8_t *buf = calloc(1, data_length);
if (buf == NULL) {
return;
}
memcpy(buf, data + GAME_PACKET_HEADER_SIZE, data_length);
Friends.list[friend_number].game_invite.data = buf;
}
Friends.list[friend_number].game_invite.type = type;
Friends.list[friend_number].game_invite.id = id;
Friends.list[friend_number].game_invite.pending = true;
Friends.list[friend_number].game_invite.data_length = data_length;
char name[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, name, friend_number);
if (self->active_box != -1) {
box_notify2(self, generic_message, NT_WNDALERT_2 | user_settings->bell_on_invite, self->active_box,
"invites you to play %s", game_string);
} else {
box_notify(self, generic_message, NT_WNDALERT_2 | user_settings->bell_on_invite, &self->active_box, name,
"invites you to play %s", game_string);
}
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s has invited you to a game of %s.", name, game_string);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Type \"/play\" to join the game.");
}
#endif // GAMES
/* AV Stuff */
#ifdef AUDIO
@ -771,7 +846,7 @@ void chat_onInvite(ToxWindow *self, ToxAV *av, uint32_t friend_number, int state
/* call is flagged active here */
self->is_call = true;
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Incoming audio call! Type: \"/answer\" or \"/reject\"");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Incoming audio call! Type: \"/answer\" or \"/reject\"");
if (self->ringing_sound == -1) {
sound_notify(self, call_incoming, NT_LOOP | user_settings->bell_on_invite, &self->ringing_sound);
@ -793,7 +868,7 @@ void chat_onRinging(ToxWindow *self, ToxAV *av, uint32_t friend_number, int stat
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Ringing...type \"/hangup\" to cancel it.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Ringing...type \"/hangup\" to cancel it.");
#ifdef SOUND_NOTIFY
@ -815,7 +890,7 @@ void chat_onStarting(ToxWindow *self, ToxAV *av, uint32_t friend_number, int sta
init_infobox(self);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call started! Type: \"/hangup\" to end it.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Call started! Type: \"/hangup\" to end it.");
/* call is flagged active here */
self->is_call = true;
@ -835,7 +910,7 @@ void chat_onEnding(ToxWindow *self, ToxAV *av, uint32_t friend_number, int state
}
kill_infobox(self);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call ended!");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Call ended!");
self->is_call = false;
@ -854,7 +929,7 @@ void chat_onError(ToxWindow *self, ToxAV *av, uint32_t friend_number, int state)
}
self->is_call = false;
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Error!");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Error!");
#ifdef SOUND_NOTIFY
stop_sound(self->ringing_sound);
@ -875,7 +950,7 @@ void chat_onStart(ToxWindow *self, ToxAV *av, uint32_t friend_number, int state)
init_infobox(self);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call started! Type: \"/hangup\" to end it.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Call started! Type: \"/hangup\" to end it.");
#ifdef SOUND_NOTIFY
stop_sound(self->ringing_sound);
@ -893,7 +968,7 @@ void chat_onCancel(ToxWindow *self, ToxAV *av, uint32_t friend_number, int state
self->is_call = false;
kill_infobox(self);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call canceled!");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Call canceled!");
#ifdef SOUND_NOTIFY
stop_sound(self->ringing_sound);
@ -909,7 +984,7 @@ void chat_onReject(ToxWindow *self, ToxAV *av, uint32_t friend_number, int state
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Rejected!");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Rejected!");
self->is_call = false;
#ifdef SOUND_NOTIFY
@ -927,7 +1002,7 @@ void chat_onEnd(ToxWindow *self, ToxAV *av, uint32_t friend_number, int state)
}
kill_infobox(self);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Call ended!");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Call ended!");
self->is_call = false;
#ifdef SOUND_NOTIFY
@ -954,7 +1029,7 @@ static void init_infobox(ToxWindow *self)
ctx->infobox.win = newwin(INFOBOX_HEIGHT, INFOBOX_WIDTH + 1, 1, x2 - INFOBOX_WIDTH);
ctx->infobox.starttime = get_unix_time();
ctx->infobox.vad_lvl = 0.0f;
ctx->infobox.vad_lvl = user_settings->VAD_threshold;
ctx->infobox.active = true;
strcpy(ctx->infobox.timestr, "00");
}
@ -992,13 +1067,12 @@ static void draw_infobox(ToxWindow *self)
time_t curtime = get_unix_time();
/* update elapsed time string once per second */
if (curtime > infobox->lastupdate) {
/* update interface once per second */
if (timed_out(infobox->lastupdate, 1)) {
get_elapsed_time_str(infobox->timestr, sizeof(infobox->timestr), curtime - infobox->starttime);
infobox->lastupdate = curtime;
}
infobox->lastupdate = curtime;
const char *in_is_muted = infobox->in_is_muted ? "yes" : "no";
const char *out_is_muted = infobox->out_is_muted ? "yes" : "no";
@ -1027,7 +1101,7 @@ static void draw_infobox(ToxWindow *self)
wattroff(infobox->win, A_BOLD);
wprintw(infobox->win, "%.2f\n", (double) infobox->vad_lvl);
wborder(infobox->win, ACS_VLINE, ' ', ACS_HLINE, ACS_HLINE, ACS_TTEE, ' ', ACS_LLCORNER, ' ');
wborder(infobox->win, ACS_VLINE, ' ', ACS_HLINE, ACS_HLINE, ACS_ULCORNER, ' ', ACS_LLCORNER, ' ');
wnoutrefresh(infobox->win);
}
@ -1045,10 +1119,7 @@ static void send_action(ToxWindow *self, ChatContext *ctx, Tox *m, char *action)
size_t len = tox_self_get_name_size(m);
selfname[len] = '\0';
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
int id = line_info_add(self, timefrmt, selfname, NULL, OUT_ACTION, 0, 0, "%s", action);
int id = line_info_add(self, true, selfname, NULL, OUT_ACTION, 0, 0, "%s", action);
cqueue_add(ctx->cqueue, action, strlen(action), OUT_ACTION, id);
}
@ -1083,7 +1154,7 @@ bool chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
input_new_char(self, key, x, x2);
if (ctx->line[0] != '/' && !ctx->self_is_typing && statusbar->connection != TOX_CONNECTION_NONE) {
set_self_typingstatus(self, m, 1);
set_self_typingstatus(self, m, true);
}
return true;
@ -1093,11 +1164,7 @@ bool chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
return true;
}
if (input_handle(self, key, x, x2)) {
return true;
}
int input_ret = false;
int input_ret = input_handle(self, key, x, x2);
if (key == L'\t' && ctx->len > 1 && ctx->line[0] == '/') { /* TAB key: auto-complete */
input_ret = true;
@ -1146,7 +1213,7 @@ bool chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
wstrsubst(ctx->line, L'', L'\n');
char line[MAX_STR_SIZE] = {0};
char line[MAX_STR_SIZE];
if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) {
memset(line, 0, sizeof(line));
@ -1161,28 +1228,27 @@ bool chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
} else {
execute(ctx->history, self, m, line, CHAT_COMMAND_MODE);
}
} else {
} else if (line[0]) {
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';
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
int id = line_info_add(self, timefrmt, selfname, NULL, OUT_MSG, 0, 0, "%s", line);
int id = line_info_add(self, true, selfname, NULL, OUT_MSG, 0, 0, "%s", line);
cqueue_add(ctx->cqueue, line, strlen(line), OUT_MSG, id);
} else {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Failed to parse message.");
}
}
wclear(ctx->linewin);
wmove(self->window, y2 - CURS_Y_OFFSET, 0);
wmove(self->window, y2, 0);
reset_buf(ctx);
}
if (ctx->len <= 0 && ctx->self_is_typing) {
set_self_typingstatus(self, m, 0);
set_self_typingstatus(self, m, false);
}
return input_ret;
@ -1190,7 +1256,8 @@ bool chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
static void chat_onDraw(ToxWindow *self, Tox *m)
{
int x2, y2;
int x2;
int y2;
getmaxyx(self->window, y2, x2);
if (y2 <= 0 || x2 <= 0) {
@ -1198,77 +1265,119 @@ static void chat_onDraw(ToxWindow *self, Tox *m)
}
ChatContext *ctx = self->chatwin;
StatusBar *statusbar = self->stb;
pthread_mutex_lock(&Winthread.lock);
line_info_print(self);
Tox_Connection connection = statusbar->connection;
Tox_User_Status status = statusbar->status;
const bool is_typing = Friends.list[self->num].is_typing;
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]);
mvwprintw(ctx->linewin, 0, 0, "%ls", &ctx->line[ctx->start]);
}
/* Draw status bar */
StatusBar *statusbar = self->stb;
mvwhline(statusbar->topline, 1, 0, ACS_HLINE, x2);
curs_set(1);
wmove(statusbar->topline, 0, 0);
/* Draw name, status and note in statusbar */
if (statusbar->connection != TOX_CONNECTION_NONE) {
int colour = MAGENTA;
Tox_User_Status status = statusbar->status;
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, " [");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
switch (connection) {
case TOX_CONNECTION_TCP:
wattron(statusbar->topline, A_BOLD | COLOR_PAIR(STATUS_ONLINE));
wprintw(statusbar->topline, "TCP");
wattroff(statusbar->topline, A_BOLD | COLOR_PAIR(STATUS_ONLINE));
break;
case TOX_CONNECTION_UDP:
wattron(statusbar->topline, A_BOLD | COLOR_PAIR(STATUS_ONLINE));
wprintw(statusbar->topline, "UDP");
wattroff(statusbar->topline, A_BOLD | COLOR_PAIR(STATUS_ONLINE));
break;
default:
wattron(statusbar->topline, COLOR_PAIR(BAR_TEXT));
wprintw(statusbar->topline, "Offline");
wattroff(statusbar->topline, COLOR_PAIR(BAR_TEXT));
break;
}
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, "] ");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
const char *status_text = "ERROR";
int colour = BAR_TEXT;
if (connection != TOX_CONNECTION_NONE) {
switch (status) {
case TOX_USER_STATUS_NONE:
colour = GREEN;
break;
case TOX_USER_STATUS_AWAY:
colour = YELLOW;
colour = STATUS_AWAY;
status_text = "Away";
break;
case TOX_USER_STATUS_BUSY:
colour = RED;
colour = STATUS_BUSY;
status_text = "Busy";
break;
default:
break;
}
}
if (colour != BAR_TEXT) {
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, "[");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wattron(statusbar->topline, COLOR_PAIR(colour) | A_BOLD);
wprintw(statusbar->topline, " %s", ONLINE_CHAR);
wprintw(statusbar->topline, "%s", status_text);
wattroff(statusbar->topline, COLOR_PAIR(colour) | A_BOLD);
if (Friends.list[self->num].is_typing) {
wattron(statusbar->topline, COLOR_PAIR(YELLOW));
}
wattron(statusbar->topline, A_BOLD);
wprintw(statusbar->topline, " %s ", statusbar->nick);
wattroff(statusbar->topline, A_BOLD);
if (Friends.list[self->num].is_typing) {
wattroff(statusbar->topline, COLOR_PAIR(YELLOW));
}
} else {
wprintw(statusbar->topline, " %s", OFFLINE_CHAR);
wattron(statusbar->topline, A_BOLD);
wprintw(statusbar->topline, " %s ", statusbar->nick);
wattroff(statusbar->topline, A_BOLD);
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, "] ");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
}
if (is_typing) {
wattron(statusbar->topline, A_BOLD | COLOR_PAIR(BAR_NOTIFY));
} else {
wattron(statusbar->topline, COLOR_PAIR(BAR_TEXT));
}
wprintw(statusbar->topline, "%s", statusbar->nick);
if (is_typing) {
wattroff(statusbar->topline, A_BOLD | COLOR_PAIR(BAR_NOTIFY));
} else {
wattroff(statusbar->topline, A_BOLD | COLOR_PAIR(BAR_TEXT));
}
/* Reset statusbar->statusmsg on window resize */
if (x2 != self->x) {
char statusmsg[TOX_MAX_STATUS_MESSAGE_LENGTH] = {'\0'};
pthread_mutex_lock(&Winthread.lock);
tox_friend_get_status_message(m, self->num, (uint8_t *) statusmsg, NULL);
size_t s_len = tox_friend_get_status_message_size(m, self->num, NULL);
pthread_mutex_unlock(&Winthread.lock);
filter_str(statusmsg, s_len);
snprintf(statusbar->statusmsg, sizeof(statusbar->statusmsg), "%s", statusmsg);
statusbar->statusmsg_len = strlen(statusbar->statusmsg);
pthread_mutex_unlock(&Winthread.lock);
}
self->x = x2;
@ -1276,37 +1385,66 @@ static void chat_onDraw(ToxWindow *self, Tox *m)
/* Truncate note if it doesn't fit in statusbar */
size_t maxlen = x2 - getcurx(statusbar->topline) - (KEY_IDENT_DIGITS * 2) - 6;
if (statusbar->statusmsg_len > maxlen) {
statusbar->statusmsg[maxlen - 3] = '\0';
pthread_mutex_lock(&Winthread.lock);
size_t statusmsg_len = statusbar->statusmsg_len;
pthread_mutex_unlock(&Winthread.lock);
if (statusmsg_len > maxlen) {
pthread_mutex_lock(&Winthread.lock);
statusbar->statusmsg[maxlen - 3] = 0;
strcat(statusbar->statusmsg, "...");
statusbar->statusmsg_len = maxlen;
pthread_mutex_unlock(&Winthread.lock);
}
if (statusbar->statusmsg[0]) {
wprintw(statusbar->topline, ": %s ", statusbar->statusmsg);
if (statusmsg_len > 0) {
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, " | ");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wattron(statusbar->topline, COLOR_PAIR(BAR_TEXT));
pthread_mutex_lock(&Winthread.lock);
wprintw(statusbar->topline, "%s ", statusbar->statusmsg);
pthread_mutex_unlock(&Winthread.lock);
} else {
wattron(statusbar->topline, COLOR_PAIR(BAR_TEXT));
}
wclrtoeol(statusbar->topline);
int s_y;
int s_x;
getyx(statusbar->topline, s_y, s_x);
mvwhline(statusbar->topline, s_y, s_x, ' ', x2 - s_x - (KEY_IDENT_DIGITS * 2) - 3);
wattroff(statusbar->topline, COLOR_PAIR(BAR_TEXT));
wmove(statusbar->topline, 0, x2 - (KEY_IDENT_DIGITS * 2) - 3);
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, "{");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
size_t i;
wattron(statusbar->topline, COLOR_PAIR(BAR_TEXT));
for (i = 0; i < KEY_IDENT_DIGITS; ++i) {
for (size_t i = 0; i < KEY_IDENT_DIGITS; ++i) {
wprintw(statusbar->topline, "%02X", Friends.list[self->num].pub_key[i] & 0xff);
}
wprintw(statusbar->topline, "}\n");
wattroff(statusbar->topline, COLOR_PAIR(BAR_TEXT));
mvwhline(self->window, y2 - CHATBOX_HEIGHT, 0, ACS_HLINE, x2);
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, "} ");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
int y, x;
int y;
int x;
getyx(self->window, y, x);
UNUSED_VAR(x);
int new_x = ctx->start ? x2 - 1 : MAX(0, wcswidth(ctx->line, ctx->pos));
wmove(self->window, y + 1, new_x);
wmove(self->window, y, new_x);
draw_window_bar(self);
wnoutrefresh(self->window);
@ -1323,14 +1461,43 @@ static void chat_onDraw(ToxWindow *self, Tox *m)
}
pthread_mutex_lock(&Winthread.lock);
refresh_file_transfer_progress(self, self->num);
if (refresh_file_transfer_progress(self, self->num)) {
flag_interface_refresh();
}
pthread_mutex_unlock(&Winthread.lock);
}
static void chat_init_log(ToxWindow *self, Tox *m, const char *self_nick)
{
ChatContext *ctx = self->chatwin;
char myid[TOX_ADDRESS_SIZE];
tox_self_get_address(m, (uint8_t *) myid);
if (log_init(ctx->log, self_nick, myid, Friends.list[self->num].pub_key, LOG_TYPE_CHAT) != 0) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to initialize chat log.");
return;
}
if (load_chat_history(self, ctx->log) != 0) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to load chat history.");
}
if (Friends.list[self->num].logging_on) {
if (log_enable(ctx->log) != 0) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to enable chat log.");
}
}
}
static void chat_onInit(ToxWindow *self, Tox *m)
{
curs_set(1);
int x2, y2;
int x2;
int y2;
getmaxyx(self->window, y2, x2);
if (y2 <= 0 || x2 <= 0) {
@ -1341,6 +1508,7 @@ static void chat_onInit(ToxWindow *self, Tox *m)
/* Init statusbar info */
StatusBar *statusbar = self->stb;
statusbar->status = get_friend_status(self->num);
statusbar->connection = get_friend_connection_status(self->num);
@ -1363,9 +1531,10 @@ static void chat_onInit(ToxWindow *self, Tox *m)
/* Init subwindows */
ChatContext *ctx = self->chatwin;
statusbar->topline = subwin(self->window, 2, x2, 0, 0);
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, x2, 0, 0);
ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - CHATBOX_HEIGHT, 0);
statusbar->topline = subwin(self->window, TOP_BAR_HEIGHT, x2, 0, 0);
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2, 0, 0);
self->window_bar = subwin(self->window, WINDOW_BAR_HEIGHT, x2, y2 - (CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT), 0);
ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - WINDOW_BAR_HEIGHT, 0);
ctx->hst = calloc(1, sizeof(struct history));
ctx->log = calloc(1, sizeof(struct chatlog));
@ -1377,19 +1546,9 @@ static void chat_onInit(ToxWindow *self, Tox *m)
line_info_init(ctx->hst);
char myid[TOX_ADDRESS_SIZE];
tox_self_get_address(m, (uint8_t *) myid);
chat_init_log(self, m, nick);
int log_ret = log_enable(nick, myid, Friends.list[self->num].pub_key, ctx->log, LOG_CHAT);
load_chat_history(self, ctx->log);
if (!Friends.list[self->num].logging_on) {
log_disable(ctx->log);
} else if (log_ret == -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);
execute(ctx->history, self, m, "/log", GLOBAL_COMMAND_MODE); // Print log status to screen
scrollok(ctx->history, 0);
wmove(self->window, y2 - CURS_Y_OFFSET, 0);
@ -1436,6 +1595,10 @@ ToxWindow *new_chat(Tox *m, uint32_t friendnum)
ret->ringing_sound = -1;
#endif /* AUDIO */
#ifdef GAMES
ret->onGameInvite = &chat_onGameInvite;
#endif /* GAMES */
ret->active_box = -1;
char nick[TOX_MAX_NAME_LENGTH];

View File

@ -41,7 +41,7 @@ void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar
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;
}
@ -50,11 +50,17 @@ 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;
}
struct FileTransfer *ft = NULL;
// first check transfer queue
if (file_send_queue_remove(self->num, idx) == 0) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Pending file transfer removed from queue");
return;
}
FileTransfer *ft = NULL;
/* cancel an incoming file transfer */
if (strcasecmp(inoutstr, "in") == 0) {
@ -62,17 +68,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;
}
@ -85,25 +91,25 @@ void cmd_conference_invite(WINDOW *window, ToxWindow *self, Tox *m, int argc, ch
UNUSED_VAR(window);
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference number required.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Conference number required.");
return;
}
long int conferencenum = strtol(argv[1], NULL, 10);
if ((conferencenum == 0 && strcmp(argv[1], "0")) || conferencenum < 0 || conferencenum == LONG_MAX) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid conference number.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid conference number.");
return;
}
Tox_Err_Conference_Invite err;
if (!tox_conference_invite(m, self->num, conferencenum, &err)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to invite contact to conference (error %d)", 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 Conference %ld.", conferencenum);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invited contact to Conference %ld.", conferencenum);
}
void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
@ -113,7 +119,7 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
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;
}
@ -122,7 +128,7 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
uint8_t type = Friends.list[self->num].conference_invite.type;
if (!Friends.list[self->num].conference_invite.pending) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "No pending conference invite.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending conference invite.");
return;
}
@ -133,7 +139,7 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
conferencenum = tox_conference_join(m, self->num, (const uint8_t *) conferencekey, length, &err);
if (err != TOX_ERR_CONFERENCE_JOIN_OK) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference instance failed to initialize (error %d)", err);
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) {
@ -142,21 +148,21 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
audio_conference_callback, NULL);
if (conferencenum == (uint32_t) -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio conference instance failed to initialize");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Audio conference instance failed to initialize");
return;
}
#else
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio support disabled by compile-time option.");
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, NULL, NULL, NULL, SYS_MSG, 0, 0, "Unknown conference type %d", type);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Unknown conference type %d", type);
return;
}
if (init_conference_win(m, conferencenum, type, NULL, 0) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference window failed to initialize.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Conference window failed to initialize.");
tox_conference_delete(m, conferencenum, NULL);
return;
}
@ -165,38 +171,89 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
if (type == TOX_CONFERENCE_TYPE_AV) {
if (!init_conference_audio_input(m, conferencenum)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio capture failed; use \"/audio on\" to try again.");
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);
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;
}
@ -213,12 +270,12 @@ void cmd_savefile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv
goto on_recv_error;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Saving file [%ld] 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;
@ -229,23 +286,23 @@ 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;
}
}
@ -257,7 +314,7 @@ void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv
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;
}
@ -266,21 +323,21 @@ void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv
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 `%s` not found.", path);
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;
}
@ -296,7 +353,7 @@ void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv
goto on_send_error;
}
struct FileTransfer *ft = new_file_transfer(self, self->num, filenum, FILE_TRANSFER_SEND, TOX_FILE_KIND_DATA);
FileTransfer *ft = new_file_transfer(self, self->num, filenum, FILE_TRANSFER_SEND, TOX_FILE_KIND_DATA);
if (!ft) {
err = TOX_ERR_FILE_SEND_TOO_MANY;
@ -310,35 +367,68 @@ 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;
on_send_error:
fclose(file_to_send);
switch (err) {
case TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND:
case TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND: {
errmsg = "File transfer failed: Invalid friend.";
break;
}
case TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED:
errmsg = "File transfer failed: Friend is offline.";
break;
case TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED: {
int queue_idx = file_send_queue_add(self->num, path, path_len);
case TOX_ERR_FILE_SEND_NAME_TOO_LONG:
char msg[MAX_STR_SIZE];
switch (queue_idx) {
case -1: {
snprintf(msg, sizeof(msg), "Invalid file name: path is null or length is zero.");
break;
}
case -2: {
snprintf(msg, sizeof(msg), "File name is too long.");
break;
}
case -3: {
snprintf(msg, sizeof(msg), "File send queue is full.");
break;
}
default: {
snprintf(msg, sizeof(msg), "File transfer queued. Type \"/cancel out %d\" to cancel.", queue_idx);
break;
}
}
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", msg);
return;
}
case TOX_ERR_FILE_SEND_NAME_TOO_LONG: {
errmsg = "File transfer failed: Filename is too long.";
break;
}
case TOX_ERR_FILE_SEND_TOO_MANY:
case TOX_ERR_FILE_SEND_TOO_MANY: {
errmsg = "File transfer failed: Too many concurrent file transfers.";
break;
}
default:
default: {
errmsg = "File transfer failed.";
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

@ -29,6 +29,7 @@
void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, 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]);

View File

@ -63,7 +63,8 @@
#include "toxic_strings.h"
#include "windows.h"
extern char *DATA_FILE;
#define MAX_CONFERENCE_NUM (MAX_WINDOWS_NUM - 2)
#define CONFERENCE_EVENT_WAIT 30
static ConferenceChat conferences[MAX_CONFERENCE_NUM];
static int max_conference_index = 0;
@ -85,6 +86,9 @@ static const char *conference_cmd_list[] = {
"/decline",
"/exit",
"/conference",
#ifdef GAMES
"/game",
#endif
"/help",
"/log",
#ifdef AUDIO
@ -100,6 +104,7 @@ static const char *conference_cmd_list[] = {
"/quit",
"/requests",
#ifdef AUDIO
"/ptt",
"/sense",
#endif
"/status",
@ -114,6 +119,25 @@ static const char *conference_cmd_list[] = {
static ToxWindow *new_conference_chat(uint32_t conferencenum);
void conference_set_title(ToxWindow *self, uint32_t conferencesnum, const char *title, size_t length)
{
ConferenceChat *chat = &conferences[conferencesnum];
if (!chat->active) {
return;
}
if (length > CONFERENCE_MAX_TITLE_LENGTH) {
length = CONFERENCE_MAX_TITLE_LENGTH;
}
memcpy(chat->title, title, length);
chat->title[length] = 0;
chat->title_length = length;
set_window_title(self, title, length);
}
static void kill_conference_window(ToxWindow *self)
{
ChatContext *ctx = self->chatwin;
@ -130,8 +154,35 @@ static void kill_conference_window(ToxWindow *self)
del_window(self);
}
int init_conference_win(Tox *m, uint32_t conferencenum, uint8_t type, const char *title,
size_t title_length)
static void init_conference_logging(ToxWindow *self, Tox *m, uint32_t conferencenum)
{
ChatContext *ctx = self->chatwin;
char my_id[TOX_ADDRESS_SIZE];
tox_self_get_address(m, (uint8_t *) my_id);
char conference_id[TOX_CONFERENCE_ID_SIZE];
tox_conference_get_id(m, conferencenum, (uint8_t *) conference_id);
if (log_init(ctx->log, conferences[self->num].title, my_id, conference_id, LOG_TYPE_CHAT) != 0) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Warning: Log failed to initialize.");
return;
}
if (load_chat_history(self, ctx->log) != 0) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to load chat history.");
}
if (user_settings->autolog == AUTOLOG_ON) {
if (log_enable(ctx->log) != 0) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to enable chat log.");
}
}
execute(ctx->history, self, m, "/log", GLOBAL_COMMAND_MODE); // print log state to screen
}
int init_conference_win(Tox *m, uint32_t conferencenum, uint8_t type, const char *title, size_t length)
{
if (conferencenum > MAX_CONFERENCE_NUM) {
return -1;
@ -154,8 +205,15 @@ int init_conference_win(Tox *m, uint32_t conferencenum, uint8_t type, const char
conferences[i].audio_enabled = false;
conferences[i].last_sent_audio = 0;
#ifdef AUDIO
conferences[i].push_to_talk_enabled = user_settings->push_to_talk;
#endif
set_active_window_index(conferences[i].chatwin);
set_window_title(self, title, title_length);
conference_set_title(self, conferencenum, title, length);
init_conference_logging(self, m, conferencenum);
if (i == max_conference_index) {
++max_conference_index;
@ -225,6 +283,25 @@ static void delete_conference(ToxWindow *self, Tox *m, uint32_t conferencenum)
free_conference(self, conferencenum);
}
void conference_rename_log_path(Tox *m, uint32_t conferencenum, const char *new_title)
{
ConferenceChat *chat = &conferences[conferencenum];
if (!chat->active) {
return;
}
char myid[TOX_ADDRESS_SIZE];
tox_self_get_address(m, (uint8_t *) myid);
char conference_id[TOX_CONFERENCE_ID_SIZE];
tox_conference_get_id(m, conferencenum, (uint8_t *) conference_id);
if (rename_logfile(chat->title, new_title, myid, conference_id, chat->chatwin) != 0) {
fprintf(stderr, "Failed to rename conference log to `%s`\n", new_title);
}
}
/* destroys and re-creates conference window with or without the peerlist */
void redraw_conference_win(ToxWindow *self)
{
@ -234,9 +311,9 @@ void redraw_conference_win(ToxWindow *self)
refresh();
clear();
int x2, y2;
getmaxyx(stdscr, y2, x2);
y2 -= 2;
int x2;
int y2;
getmaxyx(self->window, y2, x2);
if (y2 <= 0 || x2 <= 0) {
return;
@ -249,20 +326,22 @@ void redraw_conference_win(ToxWindow *self)
delwin(ctx->linewin);
delwin(ctx->history);
delwin(self->window_bar);
delwin(self->window);
self->window = newwin(y2, x2, 0, 0);
ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - CHATBOX_HEIGHT, 0);
self->window_bar = subwin(self->window, WINDOW_BAR_HEIGHT, x2, y2 - (CHATBOX_HEIGHT + WINDOW_BAR_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);
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2 - SIDEBAR_WIDTH - 1, 0, 0);
ctx->sidebar = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, SIDEBAR_WIDTH, 0, x2 - SIDEBAR_WIDTH);
} else {
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, x2, 0, 0);
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2, 0, 0);
}
scrollok(ctx->history, 0);
wmove(self->window, y2 - CURS_Y_OFFSET, 0);
}
static void conference_onConferenceMessage(ToxWindow *self, Tox *m, uint32_t conferencenum, uint32_t peernum,
@ -289,12 +368,12 @@ static void conference_onConferenceMessage(ToxWindow *self, Tox *m, uint32_t con
/* 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);
box_notify2(self, generic_message, NT_WNDALERT_0 | NT_NOFOCUS | user_settings->bell_on_message,
self->active_box, "%s %s", nick, msg);
} else {
box_silent_notify(self, NT_NOFOCUS, &self->active_box, self->name, "%s %s", nick, msg);
box_notify(self, generic_message, NT_WNDALERT_0 | NT_NOFOCUS | user_settings->bell_on_message,
&self->active_box, self->name, "%s %s", nick, msg);
}
nick_clr = RED;
@ -302,10 +381,7 @@ static void conference_onConferenceMessage(ToxWindow *self, Tox *m, uint32_t con
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, type == TOX_MESSAGE_TYPE_NORMAL ? IN_MSG : IN_ACTION, 0, nick_clr, "%s", msg);
line_info_add(self, true, nick, NULL, type == TOX_MESSAGE_TYPE_NORMAL ? IN_MSG : IN_ACTION, 0, nick_clr, "%s", msg);
write_to_log(msg, nick, ctx->log, false);
}
@ -319,19 +395,24 @@ static void conference_onConferenceTitleChange(ToxWindow *self, Tox *m, uint32_t
return;
}
set_window_title(self, title, length);
ConferenceChat *chat = &conferences[conferencenum];
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
if (!chat->active) {
return;
}
conference_rename_log_path(m, conferencenum, title); // must be called first
conference_set_title(self, conferencenum, title, length);
/* don't announce title when we join the room */
if (!timed_out(conferences[self->num].start_time, CONFERENCE_EVENT_WAIT)) {
if (!timed_out(conferences[conferencenum].start_time, CONFERENCE_EVENT_WAIT)) {
return;
}
char nick[TOX_MAX_NAME_LENGTH];
get_conference_nick_truncate(m, nick, peernum, conferencenum);
line_info_add(self, timefrmt, nick, NULL, NAME_CHANGE, 0, 0, " set the conference title to: %s", title);
line_info_add(self, true, nick, NULL, NAME_CHANGE, 0, 0, " set the conference title to: %s", title);
char tmp_event[MAX_STR_SIZE];
snprintf(tmp_event, sizeof(tmp_event), "set title to %s", title);
@ -427,8 +508,7 @@ static void conference_update_name_list(uint32_t conferencenum)
if (peer->active) {
memcpy(entry->name, peer->name, peer->name_length + 1);
bin_pubkey_to_string(peer->pubkey, sizeof(peer->pubkey),
entry->pubkey_str, sizeof(entry->pubkey_str));
tox_pk_bytes_to_str(peer->pubkey, sizeof(peer->pubkey), entry->pubkey_str, sizeof(entry->pubkey_str));
entry->peernum = i;
++count;
}
@ -492,6 +572,22 @@ static ConferencePeer *peer_in_conference(uint32_t conferencenum, uint32_t peern
}
#ifdef AUDIO
/* Return true if ptt is disabled or enabled and active. */
static bool conference_check_push_to_talk(ConferenceChat *chat)
{
if (!chat->push_to_talk_enabled) {
return true;
}
return !timed_out(chat->ptt_last_pushed, 1);
}
static void conference_enable_push_to_talk(ConferenceChat *chat)
{
chat->ptt_last_pushed = get_unix_time();
}
static void set_peer_audio_position(Tox *m, uint32_t conferencenum, uint32_t peernum)
{
ConferenceChat *chat = &conferences[conferencenum];
@ -523,7 +619,7 @@ static void set_peer_audio_position(Tox *m, uint32_t conferencenum, uint32_t pee
const float angle = asinf(peer_posn - (float)(num_posns - 1) / 2);
set_source_position(peer->audio_out_idx, sinf(angle), cosf(angle), 0);
}
#endif
#endif // AUDIO
static bool find_peer_by_pubkey(const ConferencePeer *list, uint32_t num_peers, uint8_t *pubkey, uint32_t *idx)
@ -532,7 +628,10 @@ static bool find_peer_by_pubkey(const ConferencePeer *list, uint32_t num_peers,
const ConferencePeer *peer = &list[i];
if (peer->active && memcmp(peer->pubkey, pubkey, TOX_PUBLIC_KEY_SIZE) == 0) {
*idx = i;
if (idx) {
*idx = i;
}
return true;
}
}
@ -540,7 +639,8 @@ static bool find_peer_by_pubkey(const ConferencePeer *list, uint32_t num_peers,
return false;
}
static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers, uint32_t old_num_peers)
static void update_peer_list(ToxWindow *self, Tox *m, uint32_t conferencenum, uint32_t num_peers,
uint32_t old_num_peers)
{
ConferenceChat *chat = &conferences[conferencenum];
@ -548,6 +648,8 @@ static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers,
return;
}
ChatContext *ctx = self->chatwin;
ConferencePeer *old_peer_list = malloc(old_num_peers * sizeof(ConferencePeer));
if (!old_peer_list) {
@ -559,7 +661,11 @@ static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers,
memcpy(old_peer_list, chat->peer_list, old_num_peers * sizeof(ConferencePeer));
}
realloc_peer_list(chat, num_peers);
if (realloc_peer_list(chat, num_peers) != 0) {
free(old_peer_list);
fprintf(stderr, "Warning: realloc_peer_list() failed in update_peer_list()\n");
return;
}
for (uint32_t i = 0; i < num_peers; ++i) {
ConferencePeer *peer = &chat->peer_list[i];
@ -575,12 +681,14 @@ static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers,
continue;
}
bool new_peer = true;
uint32_t j;
if (find_peer_by_pubkey(old_peer_list, old_num_peers, peer->pubkey, &j)) {
ConferencePeer *old_peer = &old_peer_list[j];
memcpy(peer, old_peer, sizeof(ConferencePeer));
old_peer->active = false;
new_peer = false;
}
size_t length = tox_conference_peer_get_name_size(m, conferencenum, i, &err);
@ -601,22 +709,34 @@ static void update_peer_list(Tox *m, uint32_t conferencenum, uint32_t num_peers,
peer->name_length = length;
peer->peernum = i;
if (new_peer && peer->name_length > 0 && timed_out(chat->start_time, CONFERENCE_EVENT_WAIT)) {
const char *msg = "has joined the conference";
line_info_add(self, true, peer->name, NULL, CONNECTION, 0, GREEN, msg);
write_to_log(msg, peer->name, ctx->log, true);
}
#ifdef AUDIO
set_peer_audio_position(m, conferencenum, i);
#endif
}
conference_update_name_list(conferencenum);
for (uint32_t i = 0; i < old_num_peers; ++i) {
ConferencePeer *old_peer = &old_peer_list[i];
if (old_peer->active) {
if (old_peer->name_length > 0 && !find_peer_by_pubkey(chat->peer_list, chat->num_peers, old_peer->pubkey, NULL)) {
const char *msg = "has left the conference";
line_info_add(self, true, old_peer->name, NULL, DISCONNECTION, 0, RED, msg);
write_to_log(msg, old_peer->name, ctx->log, true);
}
free_peer(old_peer);
}
}
free(old_peer_list);
conference_update_name_list(conferencenum);
}
static void conference_onConferenceNameListChange(ToxWindow *self, Tox *m, uint32_t conferencenum)
@ -648,7 +768,7 @@ static void conference_onConferenceNameListChange(ToxWindow *self, Tox *m, uint3
chat->num_peers = num_peers;
chat->max_idx = num_peers;
update_peer_list(m, conferencenum, num_peers, old_num);
update_peer_list(self, m, conferencenum, num_peers, old_num);
}
static void conference_onConferencePeerNameChange(ToxWindow *self, Tox *m, uint32_t conferencenum, uint32_t peernum,
@ -662,17 +782,22 @@ static void conference_onConferencePeerNameChange(ToxWindow *self, Tox *m, uint3
const ConferencePeer *peer = peer_in_conference(conferencenum, peernum);
if (peer != NULL && peer->name_length > 0) {
if (peer != NULL) {
ChatContext *ctx = self->chatwin;
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
char tmp_event[TOXIC_MAX_NAME_LENGTH * 2 + 32];
snprintf(tmp_event, sizeof(tmp_event), "is now known as %s", (const char *) name);
if (peer->name_length > 0) {
char log_event[TOXIC_MAX_NAME_LENGTH * 2 + 32];
line_info_add(self, true, peer->name, (const char *) name, NAME_CHANGE, 0, 0, " is now known as ");
write_to_log(tmp_event, peer->name, ctx->log, true);
line_info_add(self, timefrmt, peer->name, (const char *) name, NAME_CHANGE, 0, 0, " is now known as ");
snprintf(log_event, sizeof(log_event), "is now known as %s", (const char *) name);
write_to_log(log_event, peer->name, ctx->log, true);
// this is kind of a hack; peers always join a group with no name set and then set it after
} else if (timed_out(conferences[conferencenum].start_time, CONFERENCE_EVENT_WAIT)) {
const char *msg = "has joined the conference";
line_info_add(self, true, name, NULL, CONNECTION, 0, GREEN, msg);
write_to_log(msg, name, ctx->log, true);
}
}
conference_onConferenceNameListChange(self, m, conferencenum);
@ -688,7 +813,7 @@ static void send_conference_action(ToxWindow *self, ChatContext *ctx, Tox *m, ch
Tox_Err_Conference_Send_Message err;
if (!tox_conference_send_message(m, self->num, TOX_MESSAGE_TYPE_ACTION, (uint8_t *) action, strlen(action), &err)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, " * Failed to send action (error %d)", err);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Failed to send action (error %d)", err);
}
}
@ -698,7 +823,6 @@ static int sidebar_offset(uint32_t conferencenum)
return 2 + conferences[conferencenum].audio_enabled;
}
/*
* Return true if input is recognized by handler
*/
@ -741,6 +865,15 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
bool input_ret = false;
ConferenceChat *chat = &conferences[self->num];
#ifdef AUDIO
if (chat->audio_enabled && chat->push_to_talk_enabled && key == KEY_F(2)) {
input_ret = true;
conference_enable_push_to_talk(chat);
}
#endif // AUDIO
if (key == L'\t') { /* TAB key: auto-completes peer name or command */
input_ret = true;
@ -842,17 +975,19 @@ static bool conference_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
} else {
execute(ctx->history, self, m, line, CONFERENCE_COMMAND_MODE);
}
} else {
} else if (line[0]) {
Tox_Err_Conference_Send_Message err;
if (!tox_conference_send_message(m, self->num, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) line, strlen(line), &err)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, " * Failed to send message (error %d)", err);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Failed to send message (error %d)", err);
}
} else {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Failed to parse message.");
}
}
wclear(ctx->linewin);
wmove(self->window, y2 - CURS_Y_OFFSET, 0);
wmove(self->window, y2, 0);
reset_buf(ctx);
}
@ -866,15 +1001,9 @@ static void draw_peer(ToxWindow *self, Tox *m, ChatContext *ctx, uint32_t i)
const uint32_t peernum = conferences[self->num].name_list[peer_idx].peernum;
const bool is_self = tox_conference_peer_number_is_ours(m, self->num, peernum, NULL);
const bool audio = conferences[self->num].audio_enabled;
pthread_mutex_unlock(&Winthread.lock);
/* truncate nick to fit in side panel without modifying list */
char tmpnick[TOX_MAX_NAME_LENGTH];
int maxlen = SIDEBAR_WIDTH - 2 - 2 * audio;
if (audio) {
#ifdef AUDIO
pthread_mutex_lock(&Winthread.lock);
const ConferencePeer *peer = peer_in_conference(self->num, peernum);
const bool audio_active = is_self
? !timed_out(conferences[self->num].last_sent_audio, 2)
@ -891,8 +1020,14 @@ static void draw_peer(ToxWindow *self, Tox *m, ChatContext *ctx, uint32_t i)
wattroff(ctx->sidebar, aud_attr);
waddch(ctx->sidebar, ' ');
#endif
} else {
pthread_mutex_unlock(&Winthread.lock);
}
/* truncate nick to fit in side panel without modifying list */
char tmpnick[TOX_MAX_NAME_LENGTH];
int maxlen = SIDEBAR_WIDTH - 2 - 2 * audio;
pthread_mutex_lock(&Winthread.lock);
memcpy(tmpnick, &conferences[self->num].name_list[peer_idx].name, maxlen);
pthread_mutex_unlock(&Winthread.lock);
@ -921,6 +1056,12 @@ static void conference_onDraw(ToxWindow *self, Tox *m)
return;
}
ConferenceChat *chat = &conferences[self->num];
if (!chat->active) {
return;
}
ChatContext *ctx = self->chatwin;
pthread_mutex_lock(&Winthread.lock);
@ -932,19 +1073,20 @@ static void conference_onDraw(ToxWindow *self, Tox *m)
curs_set(1);
if (ctx->len > 0) {
mvwprintw(ctx->linewin, 1, 0, "%ls", &ctx->line[ctx->start]);
mvwprintw(ctx->linewin, 0, 0, "%ls", &ctx->line[ctx->start]);
}
wclear(ctx->sidebar);
mvwhline(self->window, y2 - CHATBOX_HEIGHT, 0, ACS_HLINE, x2);
if (self->show_peerlist) {
wattron(ctx->sidebar, COLOR_PAIR(BLUE));
mvwvline(ctx->sidebar, 0, 0, ACS_VLINE, y2 - CHATBOX_HEIGHT);
mvwaddch(ctx->sidebar, y2 - CHATBOX_HEIGHT, 0, ACS_BTEE);
wattroff(ctx->sidebar, COLOR_PAIR(BLUE));
pthread_mutex_lock(&Winthread.lock);
const uint32_t num_peers = conferences[self->num].num_peers;
const bool audio = conferences[self->num].audio_enabled;
const uint32_t num_peers = chat->num_peers;
const bool audio = chat->audio_enabled;
const int header_lines = sidebar_offset(self->num);
pthread_mutex_unlock(&Winthread.lock);
@ -953,32 +1095,45 @@ static void conference_onDraw(ToxWindow *self, Tox *m)
if (audio) {
#ifdef AUDIO
pthread_mutex_lock(&Winthread.lock);
const bool mic_on = !device_is_muted(input, conferences[self->num].audio_in_idx);
const bool ptt_idle = !conference_check_push_to_talk(chat) && chat->push_to_talk_enabled;
const bool mic_on = !device_is_muted(input, chat->audio_in_idx);
const float volume = get_input_volume();
const float threshold = device_get_VAD_threshold(conferences[self->num].audio_in_idx);
const float threshold = device_get_VAD_threshold(chat->audio_in_idx);
pthread_mutex_unlock(&Winthread.lock);
wmove(ctx->sidebar, line, 1);
wattron(ctx->sidebar, A_BOLD);
wprintw(ctx->sidebar, "Mic: ");
const int color = mic_on && volume > threshold ? GREEN : RED;
wattron(ctx->sidebar, COLOR_PAIR(color));
if (mic_on) {
if (!mic_on) {
wattron(ctx->sidebar, COLOR_PAIR(RED));
wprintw(ctx->sidebar, "MUTED");
wattroff(ctx->sidebar, COLOR_PAIR(RED));
} else if (ptt_idle) {
wattron(ctx->sidebar, COLOR_PAIR(GREEN));
wprintw(ctx->sidebar, "PTT");
wattroff(ctx->sidebar, COLOR_PAIR(GREEN));
} else {
const int color = volume > threshold ? GREEN : RED;
wattron(ctx->sidebar, COLOR_PAIR(color));
float v = volume;
if (v <= 0.0f) {
wprintw(ctx->sidebar, ".");
}
while (v > 0.0f) {
wprintw(ctx->sidebar, v > 10.0f ? (v > 15.0f ? "*" : "+") : (v > 5.0f ? "-" : "."));
v -= 20.0f;
}
} else {
wprintw(ctx->sidebar, "OFF");
wattroff(ctx->sidebar, COLOR_PAIR(color));
}
wattroff(ctx->sidebar, COLOR_PAIR(color));
wattroff(ctx->sidebar, A_BOLD);
++line;
#endif
#endif // AUDIO
}
wmove(ctx->sidebar, line, 1);
@ -987,8 +1142,10 @@ static void conference_onDraw(ToxWindow *self, Tox *m)
wattroff(ctx->sidebar, A_BOLD);
++line;
wattron(ctx->sidebar, COLOR_PAIR(BLUE));
mvwaddch(ctx->sidebar, line, 0, ACS_LTEE);
mvwhline(ctx->sidebar, line, 1, ACS_HLINE, SIDEBAR_WIDTH - 1);
wattroff(ctx->sidebar, COLOR_PAIR(BLUE));
++line;
for (uint32_t i = 0;
@ -1005,7 +1162,9 @@ static void conference_onDraw(ToxWindow *self, Tox *m)
UNUSED_VAR(x);
int new_x = ctx->start ? x2 - 1 : MAX(0, wcswidth(ctx->line, ctx->pos));
wmove(self->window, y + 1, new_x);
wmove(self->window, y, new_x);
draw_window_bar(self);
wnoutrefresh(self->window);
@ -1025,9 +1184,10 @@ static void conference_onInit(ToxWindow *self, Tox *m)
ChatContext *ctx = self->chatwin;
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, x2 - SIDEBAR_WIDTH - 1, 0, 0);
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2 - SIDEBAR_WIDTH - 1, 0, 0);
self->window_bar = subwin(self->window, WINDOW_BAR_HEIGHT, x2, y2 - (CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT), 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->sidebar = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, SIDEBAR_WIDTH, 0, x2 - SIDEBAR_WIDTH);
ctx->hst = calloc(1, sizeof(struct history));
ctx->log = calloc(1, sizeof(struct chatlog));
@ -1038,17 +1198,6 @@ static void conference_onInit(ToxWindow *self, Tox *m)
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_CONFERENCE) == -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);
}
@ -1094,7 +1243,6 @@ static ToxWindow *new_conference_chat(uint32_t conferencenum)
#define CONFAV_SAMPLE_RATE 48000
#define CONFAV_FRAME_DURATION 20
#define CONFAV_AUDIO_CHANNELS 1
#define CONFAV_SAMPLES_PER_FRAME (CONFAV_SAMPLE_RATE * CONFAV_FRAME_DURATION / 1000)
void audio_conference_callback(void *tox, uint32_t conferencenum, uint32_t peernum, const int16_t *pcm,
@ -1131,12 +1279,20 @@ static void conference_read_device_callback(const int16_t *captured, uint32_t si
AudioInputCallbackData *audio_input_callback_data = (AudioInputCallbackData *)data;
conferences[audio_input_callback_data->conferencenum].last_sent_audio = get_unix_time();
ConferenceChat *chat = &conferences[audio_input_callback_data->conferencenum];
if (!conference_check_push_to_talk(chat)) {
return;
}
chat->last_sent_audio = get_unix_time();
int channels = user_settings->conference_audio_channels;
toxav_group_send_audio(audio_input_callback_data->tox,
audio_input_callback_data->conferencenum,
captured, CONFAV_SAMPLES_PER_FRAME,
CONFAV_AUDIO_CHANNELS, CONFAV_SAMPLE_RATE);
channels, CONFAV_SAMPLE_RATE);
}
bool init_conference_audio_input(Tox *tox, uint32_t conferencenum)
@ -1150,9 +1306,11 @@ bool init_conference_audio_input(Tox *tox, uint32_t conferencenum)
const AudioInputCallbackData audio_input_callback_data = { tox, conferencenum };
chat->audio_input_callback_data = audio_input_callback_data;
int channels = user_settings->conference_audio_channels;
bool success = (open_input_device(&chat->audio_in_idx,
conference_read_device_callback, &chat->audio_input_callback_data, true,
CONFAV_SAMPLE_RATE, CONFAV_FRAME_DURATION, CONFAV_AUDIO_CHANNELS)
conference_read_device_callback, &chat->audio_input_callback_data,
CONFAV_SAMPLE_RATE, CONFAV_FRAME_DURATION, channels)
== de_None);
chat->audio_enabled = success;
@ -1160,7 +1318,20 @@ bool init_conference_audio_input(Tox *tox, uint32_t conferencenum)
return success;
}
bool enable_conference_audio(Tox *tox, uint32_t conferencenum)
bool toggle_conference_push_to_talk(uint32_t conferencenum, bool enabled)
{
ConferenceChat *chat = &conferences[conferencenum];
if (!chat->active) {
return false;
}
chat->push_to_talk_enabled = enabled;
return true;
}
bool enable_conference_audio(ToxWindow *self, Tox *tox, uint32_t conferencenum)
{
if (!toxav_groupchat_av_enabled(tox, conferencenum)) {
if (toxav_groupchat_enable_av(tox, conferencenum, audio_conference_callback, NULL) != 0) {
@ -1168,10 +1339,16 @@ bool enable_conference_audio(Tox *tox, uint32_t conferencenum)
}
}
return init_conference_audio_input(tox, conferencenum);
bool success = init_conference_audio_input(tox, conferencenum);
if (success) {
self->is_call = true;
}
return success;
}
bool disable_conference_audio(Tox *tox, uint32_t conferencenum)
bool disable_conference_audio(ToxWindow *self, Tox *tox, uint32_t conferencenum)
{
ConferenceChat *chat = &conferences[conferencenum];
@ -1184,7 +1361,13 @@ bool disable_conference_audio(Tox *tox, uint32_t conferencenum)
chat->audio_enabled = false;
}
return toxav_groupchat_disable_av(tox, conferencenum) == 0;
bool success = toxav_groupchat_disable_av(tox, conferencenum) == 0;
if (success) {
self->is_call = false;
}
return success;
}
bool conference_mute_self(uint32_t conferencenum)
@ -1244,4 +1427,4 @@ float conference_get_VAD_threshold(uint32_t conferencenum)
return device_get_VAD_threshold(chat->audio_in_idx);
}
#endif
#endif // AUDIO

View File

@ -26,9 +26,8 @@
#include "toxic.h"
#include "windows.h"
#define CONFERENCE_MAX_TITLE_LENGTH TOX_MAX_NAME_LENGTH
#define SIDEBAR_WIDTH 16
#define MAX_CONFERENCE_NUM MAX_WINDOWS_NUM - 2
#define CONFERENCE_EVENT_WAIT 3
typedef struct ConferencePeer {
bool active;
@ -64,12 +63,18 @@ typedef struct {
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;
@ -79,11 +84,15 @@ typedef struct {
/* 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 title_length);
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`.
@ -94,8 +103,9 @@ uint32_t get_name_list_entries_by_prefix(uint32_t conferencenum, const char *pre
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 enable_conference_audio(ToxWindow *self, Tox *tox, uint32_t conferencenum);
bool disable_conference_audio(ToxWindow *self, 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);

View File

@ -32,7 +32,7 @@
static void print_err(ToxWindow *self, const char *error_str)
{
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", 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])
@ -40,7 +40,7 @@ void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc,
UNUSED_VAR(window);
Tox_Err_Conference_Title err;
char title[MAX_STR_SIZE];
char title[CONFERENCE_MAX_TITLE_LENGTH + 1];
if (argc < 1) {
size_t tlen = tox_conference_get_title_size(m, self->num, &err);
@ -56,31 +56,36 @@ void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc,
}
title[tlen] = '\0';
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Title is set to: %s", title);
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]);
int len = strlen(title);
if (!tox_conference_set_title(m, self->num, (uint8_t *) title, len, &err)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set title (error %d)", err);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to set title (error %d)", err);
return;
}
set_window_title(self, title, len);
conference_rename_log_path(m, self->num, title); // must be called first
conference_set_title(self, self->num, 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 conference title to: %s", title);
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);
@ -103,8 +108,9 @@ void cmd_enable_audio(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*
return;
}
if (enable ? enable_conference_audio(m, self->num) : disable_conference_audio(m, self->num)) {
print_err(self, enable ? "Enabled conference audio" : "Disabled conference audio");
if (enable ? enable_conference_audio(self, m, self->num) : disable_conference_audio(self, 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");
}
@ -133,16 +139,16 @@ void cmd_conference_mute(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
print_err(self, "Multiple matching peers (use /mute [public key] to disambiguate):");
for (uint32_t i = 0; i < n; ++i) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s: %s", entries[i]->pubkey_str, entries[i]->name);
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, NULL, NULL, NULL, SYS_MSG, 0, 0, "Toggled audio mute status of %s", entries[0]->name);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Toggled audio mute status of %s", entries[0]->name);
} else {
print_err(self, "No such audio peer");
print_err(self, "Peer is not on the call");
}
}
}
@ -153,7 +159,7 @@ void cmd_conference_sense(WINDOW *window, ToxWindow *self, Tox *m, int argc, cha
UNUSED_VAR(m);
if (argc == 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Current VAD threshold: %.1f",
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Current VAD threshold: %.1f",
(double) conference_get_VAD_threshold(self->num));
return;
}
@ -172,9 +178,33 @@ void cmd_conference_sense(WINDOW *window, ToxWindow *self, Tox *m, int argc, cha
}
if (conference_set_VAD_threshold(self->num, value)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Set VAD threshold to %.1f", (double) 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 */

View File

@ -30,5 +30,6 @@ void cmd_conference_set_title(WINDOW *window, ToxWindow *self, Tox *m, int argc,
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

@ -49,6 +49,9 @@ static struct cmd_func global_commands[] = {
{ "/decline", cmd_decline },
{ "/exit", cmd_quit },
{ "/conference", cmd_conference },
#ifdef GAMES
{ "/game", cmd_game },
#endif
{ "/help", cmd_prompt_help },
{ "/log", cmd_log },
{ "/myid", cmd_myid },
@ -80,6 +83,9 @@ static struct cmd_func chat_commands[] = {
{ "/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
@ -105,20 +111,15 @@ static struct cmd_func conference_commands[] = {
#ifdef AUDIO
{ "/audio", cmd_enable_audio },
{ "/mute", cmd_conference_mute },
{ "/ptt", cmd_conference_push_to_talk },
{ "/sense", cmd_conference_sense },
#endif /* AUDIO */
{ NULL, NULL },
};
#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] = {
static const char special_commands[][MAX_CMDNAME_SIZE] = {
"/add",
"/avatar",
"/nick",
"/note",
@ -128,6 +129,7 @@ static const char special_commands[SPECIAL_COMMANDS][MAX_CMDNAME_SIZE] = {
"/sendfile",
"/title",
"/mute",
"",
};
/* Returns true if input command is in the special_commands array. */
@ -135,7 +137,7 @@ static bool is_special_command(const char *input)
{
const int s = char_find(0, input, ' ');
for (int i = 0; i < SPECIAL_COMMANDS; ++i) {
for (int i = 0; special_commands[i][0] != '\0'; ++i) {
if (strncmp(input, special_commands[i], s) == 0) {
return true;
}
@ -212,9 +214,7 @@ static int parse_command(const char *input, char (*args)[MAX_STR_SIZE])
static int do_command(WINDOW *w, ToxWindow *self, Tox *m, int num_args, struct cmd_func *commands,
char (*args)[MAX_STR_SIZE])
{
int i;
for (i = 0; commands[i].name != NULL; ++i) {
for (size_t i = 0; commands[i].name != NULL; ++i) {
if (strcmp(args[0], commands[i].name) == 0) {
(commands[i].func)(w, self, m, num_args - 1, args);
return 0;
@ -270,5 +270,5 @@ void execute(WINDOW *w, ToxWindow *self, Tox *m, const char *input, int mode)
#endif
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid command.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid command.");
}

View File

@ -25,6 +25,7 @@
#include <time.h>
#include <unistd.h>
#include "execute.h"
#include "file_transfers.h"
#include "friendlist.h"
#include "line_info.h"
@ -60,13 +61,15 @@ void print_progress_bar(ToxWindow *self, double bps, double pct_done, uint32_t l
return;
}
char pct_str[STR_BUF_SIZE] = {0};
char pct_str[STR_BUF_SIZE];
snprintf(pct_str, sizeof(pct_str), "%.1f%%", pct_done);
char bps_str[STR_BUF_SIZE] = {0};
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;
@ -96,7 +99,7 @@ void print_progress_bar(ToxWindow *self, double bps, double pct_done, uint32_t l
free(full_line);
}
static void refresh_progress_helper(ToxWindow *self, struct FileTransfer *ft)
static void refresh_progress_helper(ToxWindow *self, FileTransfer *ft)
{
if (ft->state == FILE_TRANSFER_INACTIVE) {
return;
@ -115,18 +118,32 @@ static void refresh_progress_helper(ToxWindow *self, struct FileTransfer *ft)
ft->last_line_progress = get_unix_time();
}
/* refreshes active file transfer status bars. */
void refresh_file_transfer_progress(ToxWindow *self, uint32_t friendnumber)
/* refreshes active file transfer status bars.
*
* Return true if there is at least one active file transfer in either direction.
*/
bool refresh_file_transfer_progress(ToxWindow *self, uint32_t friendnumber)
{
bool active = false;
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]);
FileTransfer *ft_r = &Friends.list[friendnumber].file_receiver[i];
FileTransfer *ft_s = &Friends.list[friendnumber].file_sender[i];
refresh_progress_helper(self, ft_r);
refresh_progress_helper(self, ft_s);
if (ft_r->state != FILE_TRANSFER_INACTIVE || ft_s->state != FILE_TRANSFER_INACTIVE) {
active = true;
}
}
return active;
}
static void clear_file_transfer(struct FileTransfer *ft)
static void clear_file_transfer(FileTransfer *ft)
{
*ft = (struct FileTransfer) {
*ft = (FileTransfer) {
0
};
}
@ -134,16 +151,16 @@ static void clear_file_transfer(struct FileTransfer *ft)
/* 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)
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];
FileTransfer *ft_send = &Friends.list[friendnumber].file_sender[i];
if (ft_send->state != FILE_TRANSFER_INACTIVE && ft_send->filenumber == filenumber) {
return ft_send;
}
struct FileTransfer *ft_recv = &Friends.list[friendnumber].file_receiver[i];
FileTransfer *ft_recv = &Friends.list[friendnumber].file_receiver[i];
if (ft_recv->state != FILE_TRANSFER_INACTIVE && ft_recv->filenumber == filenumber) {
return ft_recv;
@ -156,7 +173,7 @@ struct FileTransfer *get_file_transfer_struct(uint32_t friendnumber, uint32_t fi
/* 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 friendnumber, uint32_t index,
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) {
@ -164,9 +181,9 @@ struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint3
}
for (size_t i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = direction == FILE_TRANSFER_SEND ?
&Friends.list[friendnumber].file_sender[i] :
&Friends.list[friendnumber].file_receiver[i];
FileTransfer *ft = direction == FILE_TRANSFER_SEND ?
&Friends.list[friendnumber].file_sender[i] :
&Friends.list[friendnumber].file_receiver[i];
if (ft->state != FILE_TRANSFER_INACTIVE && ft->index == index) {
return ft;
@ -179,10 +196,10 @@ struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint3
/* 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 friendnumber, uint32_t filenumber, uint8_t type)
static FileTransfer *new_file_sender(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber, uint8_t type)
{
for (size_t i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = &Friends.list[friendnumber].file_sender[i];
FileTransfer *ft = &Friends.list[friendnumber].file_sender[i];
if (ft->state == FILE_TRANSFER_INACTIVE) {
clear_file_transfer(ft);
@ -202,11 +219,11 @@ 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 friendnumber, uint32_t filenumber,
uint8_t type)
static FileTransfer *new_file_receiver(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber,
uint8_t type)
{
for (size_t i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = &Friends.list[friendnumber].file_receiver[i];
FileTransfer *ft = &Friends.list[friendnumber].file_receiver[i];
if (ft->state == FILE_TRANSFER_INACTIVE) {
clear_file_transfer(ft);
@ -226,8 +243,8 @@ 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 friendnumber, uint32_t filenumber,
FILE_TRANSFER_DIRECTION direction, uint8_t type)
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, friendnumber, filenumber, type);
@ -240,13 +257,83 @@ struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnumber,
return NULL;
}
int file_send_queue_add(uint32_t friendnumber, const char *file_path, size_t length)
{
if (length == 0 || file_path == NULL) {
return -1;
}
if (length > TOX_MAX_FILENAME_LENGTH) {
return -2;
}
for (size_t i = 0; i < MAX_FILES; ++i) {
PendingFileTransfer *pending_slot = &Friends.list[friendnumber].file_send_queue[i];
if (pending_slot->pending) {
continue;
}
pending_slot->pending = true;
memcpy(pending_slot->file_path, file_path, length);
pending_slot->file_path[length] = 0;
pending_slot->length = length;
return i;
}
return -3;
}
#define FILE_TRANSFER_SEND_CMD "/sendfile "
#define FILE_TRANSFER_SEND_LEN (sizeof(FILE_TRANSFER_SEND_CMD) - 1)
void file_send_queue_check(ToxWindow *self, Tox *m, uint32_t friendnumber)
{
for (size_t i = 0; i < MAX_FILES; ++i) {
PendingFileTransfer *pending_slot = &Friends.list[friendnumber].file_send_queue[i];
if (!pending_slot->pending) {
continue;
}
char command[TOX_MAX_FILENAME_LENGTH + FILE_TRANSFER_SEND_LEN + 1];
snprintf(command, sizeof(command), "%s%s", FILE_TRANSFER_SEND_CMD, pending_slot->file_path);
execute(self->window, self, m, command, CHAT_COMMAND_MODE);
*pending_slot = (PendingFileTransfer) {
0,
};
}
}
int file_send_queue_remove(uint32_t friendnumber, size_t index)
{
if (index >= MAX_FILES) {
return -1;
}
PendingFileTransfer *pending_slot = &Friends.list[friendnumber].file_send_queue[index];
if (!pending_slot->pending) {
return -1;
}
*pending_slot = (PendingFileTransfer) {
0,
};
return 0;
}
/* Closes file transfer ft.
*
* Set CTRL to -1 if we don't want to send a control signal.
* Set message or self to NULL if we don't want to display a message.
*/
void close_file_transfer(ToxWindow *self, Tox *m, struct FileTransfer *ft, int CTRL, const char *message,
void close_file_transfer(ToxWindow *self, Tox *m, FileTransfer *ft, int CTRL, const char *message,
Notification sound_type)
{
if (!ft) {
@ -262,7 +349,11 @@ void close_file_transfer(ToxWindow *self, Tox *m, struct FileTransfer *ft, int C
}
if (CTRL >= 0) {
tox_file_control(m, ft->friendnumber, ft->filenumber, (Tox_File_Control) CTRL, NULL);
Tox_Err_File_Control err;
if (!tox_file_control(m, ft->friendnumber, ft->filenumber, (Tox_File_Control) CTRL, &err)) {
fprintf(stderr, "Failed to cancel file transfer: %d\n", err);
}
}
if (message && self) {
@ -272,7 +363,7 @@ void close_file_transfer(ToxWindow *self, Tox *m, struct FileTransfer *ft, int C
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);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", message);
}
clear_file_transfer(ft);
@ -282,7 +373,7 @@ void close_file_transfer(ToxWindow *self, Tox *m, struct FileTransfer *ft, int C
void kill_avatar_file_transfers_friend(Tox *m, uint32_t friendnumber)
{
for (size_t i = 0; i < MAX_FILES; ++i) {
struct FileTransfer *ft = &Friends.list[friendnumber].file_sender[i];
FileTransfer *ft = &Friends.list[friendnumber].file_sender[i];
if (ft->file_type == TOX_FILE_KIND_AVATAR) {
close_file_transfer(NULL, m, ft, TOX_FILE_CONTROL_CANCEL, NULL, silent);
@ -296,6 +387,7 @@ 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);
file_send_queue_remove(friendnumber, i);
}
}

View File

@ -29,9 +29,9 @@
#include "toxic.h"
#include "windows.h"
#define KiB 1024
#define MiB 1048576 /* 1024^2 */
#define GiB 1073741824 /* 1024^3 */
#define KiB (uint32_t) 1024
#define MiB (uint32_t) (1024 << 10) /* 1024^2 */
#define GiB (uint32_t) (1024 << 20) /* 1024^3 */
#define MAX_FILES 32
@ -47,7 +47,7 @@ typedef enum FILE_TRANSFER_DIRECTION {
FILE_TRANSFER_RECV
} FILE_TRANSFER_DIRECTION;
struct FileTransfer {
typedef struct FileTransfer {
ToxWindow *window;
FILE *file;
FILE_TRANSFER_STATE state;
@ -63,7 +63,15 @@ struct FileTransfer {
time_t last_line_progress; /* The last time we updated the progress bar */
uint32_t line_id;
uint8_t file_id[TOX_FILE_ID_LENGTH];
};
} FileTransfer;
typedef struct PendingFileTransfer {
char file_path[TOX_MAX_FILENAME_LENGTH + 1];
size_t length;
uint32_t friendnumber;
bool pending;
} PendingFileTransfer;
/* creates initial progress line that will be updated during file transfer.
progline must be at lesat MAX_STR_SIZE bytes */
@ -72,8 +80,11 @@ void init_progress_bar(char *progline);
/* prints a progress bar for file transfers */
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, uint32_t friendnumber);
/* refreshes active file transfer status bars.
*
* Return true if there is at least one active file transfer in either direction.
*/
bool refresh_file_transfer_progress(ToxWindow *self, uint32_t friendnumber);
/* Returns a pointer to friendnumber's FileTransfer struct associated with filenumber.
* Returns NULL if filenumber is invalid.
@ -93,6 +104,31 @@ struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint3
struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber,
FILE_TRANSFER_DIRECTION direction, uint8_t type);
/* Adds a file designated by `file_path` of length `length` to the file transfer queue.
*
* Items in this queue will be automatically sent to the contact designated by `friendnumber`
* as soon as they appear online. The item will then be removed from the queue whether
* or not the transfer successfully initiates.
*
* If the ToxWindow associated with this friend is closed, all queued items will be
* discarded.
*
* Return the queue index on success.
* Return -1 if the length is invalid.
* Return -2 if the send queue is full.
*/
int file_send_queue_add(uint32_t friendnumber, const char *file_path, size_t length);
/* Initiates all file transfers from the file send queue for friend designated by `friendnumber`. */
void file_send_queue_check(ToxWindow *self, Tox *m, uint32_t friendnumber);
/* Removes the `index`-th item from the file send queue for `friendnumber`.
*
* Return 0 if a pending transfer was successfully removed
* Return -1 if index does not designate a pending file transfer.
*/
int file_send_queue_remove(uint32_t friendnumber, size_t index);
/* Closes file transfer ft.
*
* Set CTRL to -1 if we don't want to send a control signal.

View File

@ -37,6 +37,7 @@
#include "log.h"
#include "misc_tools.h"
#include "notify.h"
#include "prompt.h"
#include "settings.h"
#include "toxic.h"
#include "windows.h"
@ -46,13 +47,6 @@
#endif
extern char *DATA_FILE;
extern char *BLOCK_FILE;
extern ToxWindow *prompt;
extern struct Winthread Winthread;
extern struct user_settings *user_settings;
extern struct arg_opts arg_opts;
static uint8_t blocklist_view = 0; /* 0 if we're in friendlist view, 1 if we're in blocklist view */
FriendsList Friends;
@ -116,8 +110,11 @@ static void realloc_blocklist(int n)
void kill_friendlist(ToxWindow *self)
{
for (size_t i = 0; i < Friends.max_idx; ++i) {
if (Friends.list[i].active && Friends.list[i].conference_invite.key != NULL) {
if (Friends.list[i].active) {
free(Friends.list[i].conference_invite.key);
#ifdef GAMES
free(Friends.list[i].game_invite.data);
#endif
}
}
@ -318,11 +315,11 @@ int load_blocklist(char *path)
#define S_WEIGHT 100000
static int index_name_cmp(const void *n1, const void *n2)
{
int res = qsort_strcasecmp_hlpr(Friends.list[*(int *) n1].name, Friends.list[*(int *) n2].name);
int res = qsort_strcasecmp_hlpr(Friends.list[*(const int *) n1].name, Friends.list[*(const int *) n2].name);
/* Use weight to make qsort always put online friends before offline */
res = Friends.list[*(int *) n1].connection_status ? (res - S_WEIGHT) : (res + S_WEIGHT);
res = Friends.list[*(int *) n2].connection_status ? (res + S_WEIGHT) : (res - S_WEIGHT);
res = Friends.list[*(const int *) n1].connection_status ? (res - S_WEIGHT) : (res + S_WEIGHT);
res = Friends.list[*(const int *) n2].connection_status ? (res + S_WEIGHT) : (res - S_WEIGHT);
return res;
}
@ -346,7 +343,7 @@ void sort_friendlist_index(void)
static int index_name_cmp_block(const void *n1, const void *n2)
{
return qsort_strcasecmp_hlpr(Blocked.list[*(int *) n1].name, Blocked.list[*(int *) n2].name);
return qsort_strcasecmp_hlpr(Blocked.list[*(const int *) n1].name, Blocked.list[*(const int *) n2].name);
}
static void sort_blocklist_index(void)
@ -397,11 +394,8 @@ static void friendlist_onMessage(ToxWindow *self, Tox *m, uint32_t num, Tox_Mess
char nick[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, nick, num);
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
line_info_add(prompt, timefrmt, nick, NULL, IN_MSG, 0, 0, "%s", str);
line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, RED, "* Warning: Too many windows are open.");
line_info_add(prompt, true, nick, NULL, IN_MSG, 0, 0, "%s", str);
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, RED, "* Warning: Too many windows are open.");
sound_notify(prompt, notif_error, NT_WNDALERT_1, NULL);
}
@ -453,7 +447,9 @@ static void friendlist_onNickChange(ToxWindow *self, Tox *m, uint32_t num, const
tox_self_get_address(m, (uint8_t *) myid);
if (strcmp(oldname, newnamecpy) != 0) {
rename_logfile(oldname, newnamecpy, myid, Friends.list[num].pub_key, Friends.list[num].chatwin);
if (rename_logfile(oldname, newnamecpy, myid, Friends.list[num].pub_key, Friends.list[num].chatwin) != 0) {
fprintf(stderr, "Failed to rename friend chat log from `%s` to `%s`\n", oldname, newnamecpy);
}
}
sort_friendlist_index();
@ -583,6 +579,38 @@ static void friendlist_add_blocked(uint32_t fnum, uint32_t bnum)
}
}
#ifdef GAMES
static void friendlist_onGameInvite(ToxWindow *self, Tox *m, uint32_t friend_number, const uint8_t *data, size_t length)
{
UNUSED_VAR(self);
UNUSED_VAR(data);
UNUSED_VAR(length);
if (friend_number >= Friends.max_idx) {
return;
}
if (Friends.list[friend_number].chatwin != -1) {
return;
}
if (get_num_active_windows() < MAX_WINDOWS_NUM) {
Friends.list[friend_number].chatwin = add_window(m, new_chat(m, Friends.list[friend_number].num));
return;
}
char nick[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, nick, friend_number);
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, RED,
"* Game invite from %s failed: Too many windows are open.", nick);
sound_notify(prompt, notif_error, NT_WNDALERT_1, NULL);
}
#endif // GAMES
static void friendlist_onFileRecv(ToxWindow *self, Tox *m, uint32_t num, uint32_t filenum,
uint64_t file_size, const char *filename, size_t name_length)
{
@ -609,7 +637,7 @@ static void friendlist_onFileRecv(ToxWindow *self, Tox *m, uint32_t num, uint32_
char nick[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, nick, num);
line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, RED,
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, RED,
"* File transfer from %s failed: too many windows are open.", nick);
sound_notify(prompt, notif_error, NT_WNDALERT_1, NULL);
@ -640,7 +668,7 @@ static void friendlist_onConferenceInvite(ToxWindow *self, Tox *m, int32_t num,
char nick[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, nick, num);
line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, RED,
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, RED,
"* Conference chat invite from %s failed: too many windows are open.", nick);
sound_notify(prompt, notif_error, NT_WNDALERT_1, NULL);
@ -664,6 +692,9 @@ static void select_friend(wint_t key, int *selected, int num)
static void delete_friend(Tox *m, uint32_t f_num)
{
kill_all_file_transfers_friend(m, f_num);
kill_avatar_file_transfers_friend(m, f_num);
Tox_Err_Friend_Delete err;
if (tox_friend_delete(m, f_num, &err) != true) {
@ -805,16 +836,14 @@ static void delete_blocked_friend(uint32_t bnum)
/* deletes contact from friendlist and puts in blocklist */
void block_friend(Tox *m, uint32_t fnum)
{
if (Friends.num_friends <= 0) {
if (Friends.num_friends == 0) {
return;
}
realloc_blocklist(Blocked.max_idx + 1);
clear_blocklist_index(Blocked.max_idx);
int i;
for (i = 0; i <= Blocked.max_idx; ++i) {
for (int i = 0; i <= Blocked.max_idx; ++i) {
if (Blocked.list[i].active) {
continue;
}
@ -852,7 +881,7 @@ static void unblock_friend(Tox *m, uint32_t bnum)
uint32_t friendnum = tox_friend_add_norequest(m, (uint8_t *) Blocked.list[bnum].pub_key, &err);
if (err != TOX_ERR_FRIEND_ADD_OK) {
line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to unblock friend (error %d)", err);
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to unblock friend (error %d)", err);
return;
}
@ -921,7 +950,7 @@ static bool friendlist_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
set_active_window_index(Friends.list[f].chatwin);
} else {
const char *msg = "* Warning: Too many windows are open.";
line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, RED, msg);
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, RED, msg);
sound_notify(prompt, notif_error, NT_WNDALERT_1, NULL);
}
@ -1020,7 +1049,7 @@ static void blocklist_onDraw(ToxWindow *self, Tox *m, int y2, int x2)
wmove(self->window, y2 - 1, 1);
wattron(self->window, A_BOLD);
wprintw(self->window, "Key: ");
wprintw(self->window, "Public key: ");
wattroff(self->window, A_BOLD);
int i;
@ -1055,6 +1084,8 @@ static void friendlist_onDraw(ToxWindow *self, Tox *m)
wprintw(self->window, "key for help\n\n");
wattroff(self->window, COLOR_PAIR(CYAN));
draw_window_bar(self);
if (blocklist_view == 1) {
blocklist_onDraw(self, m, y2, x2);
return;
@ -1077,14 +1108,14 @@ static void friendlist_onDraw(ToxWindow *self, Tox *m)
/* Determine which portion of friendlist to draw based on current position */
pthread_mutex_lock(&Winthread.lock);
int page = Friends.num_selected / (y2 - FLIST_OFST);
const int page = Friends.num_selected / (y2 - FLIST_OFST);
pthread_mutex_unlock(&Winthread.lock);
int start = (y2 - FLIST_OFST) * page;
int end = y2 - FLIST_OFST + start;
const int start = (y2 - FLIST_OFST) * page;
const int end = y2 - FLIST_OFST + start;
pthread_mutex_lock(&Winthread.lock);
size_t num_friends = Friends.num_friends;
const size_t num_friends = Friends.num_friends;
pthread_mutex_unlock(&Winthread.lock);
int i;
@ -1096,9 +1127,9 @@ static void friendlist_onDraw(ToxWindow *self, Tox *m)
int num_selected = Friends.num_selected;
pthread_mutex_unlock(&Winthread.lock);
bool f_selected = false;
if (is_active) {
bool f_selected = false;
if (i == num_selected) {
wattron(self->window, A_BOLD);
wprintw(self->window, " > ");
@ -1245,7 +1276,7 @@ static void friendlist_onDraw(ToxWindow *self, Tox *m)
wmove(self->window, y2 - 1, 1);
wattron(self->window, A_BOLD);
wprintw(self->window, "Key: ");
wprintw(self->window, "Public key: ");
wattroff(self->window, A_BOLD);
int i;
@ -1263,6 +1294,21 @@ static void friendlist_onDraw(ToxWindow *self, Tox *m)
}
}
void friendlist_onInit(ToxWindow *self, Tox *m)
{
UNUSED_VAR(m);
int x2;
int y2;
getmaxyx(self->window, y2, x2);
if (y2 <= 0 || x2 <= 0) {
exit_toxic_err("failed in friendlist_onInit", FATALERR_CURSES);
}
self->window_bar = subwin(self->window, WINDOW_BAR_HEIGHT, x2, y2 - 2, 0);
}
void disable_chatwin(uint32_t f_num)
{
Friends.list[f_num].chatwin = -1;
@ -1288,10 +1334,10 @@ static void friendlist_onAV(ToxWindow *self, ToxAV *av, uint32_t friend_number,
} else {
char nick[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, nick, Friends.list[friend_number].num);
line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio action from: %s!", nick);
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, 0, "Audio action from: %s!", nick);
const char *errmsg = "* Warning: Too many windows are open.";
line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, RED, errmsg);
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, RED, errmsg);
sound_notify(prompt, notif_error, NT_WNDALERT_1, NULL);
}
@ -1341,6 +1387,7 @@ ToxWindow *new_friendlist(void)
ret->type = WINDOW_TYPE_FRIEND_LIST;
ret->onInit = &friendlist_onInit;
ret->onKey = &friendlist_onKey;
ret->onDraw = &friendlist_onDraw;
ret->onFriendAdded = &friendlist_onFriendAdded;
@ -1366,6 +1413,10 @@ ToxWindow *new_friendlist(void)
ret->is_call = false;
#endif /* AUDIO */
#ifdef GAMES
ret->onGameInvite = &friendlist_onGameInvite;
#endif
ret->num = -1;
ret->active_box = -1;
@ -1376,6 +1427,6 @@ ToxWindow *new_friendlist(void)
}
ret->help = help;
strcpy(ret->name, "contacts");
strcpy(ret->name, "Contacts");
return ret;
}

View File

@ -29,6 +29,10 @@
#include "toxic.h"
#include "windows.h"
#ifdef GAMES
#include "game_base.h"
#endif
struct LastOnline {
uint64_t last_on;
struct tm tm;
@ -42,6 +46,18 @@ struct ConferenceInvite {
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];
uint16_t namelength;
@ -59,8 +75,13 @@ typedef struct {
struct LastOnline last_online;
struct ConferenceInvite conference_invite;
struct FileTransfer file_receiver[MAX_FILES];
struct FileTransfer file_sender[MAX_FILES];
#ifdef GAMES
struct GameInvite game_invite;
#endif
FileTransfer file_receiver[MAX_FILES];
FileTransfer file_sender[MAX_FILES];
PendingFileTransfer file_send_queue[MAX_FILES];
} ToxicFriend;
typedef struct {
@ -81,7 +102,10 @@ typedef struct {
ToxicFriend *list;
} FriendsList;
extern FriendsList Friends;
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);

1140
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

30
src/game_centipede.h Normal file
View File

@ -0,0 +1,30 @@
/* game_centipede.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_CENTIPEDE
#define GAME_CENTIPEDE
#include "game_base.h"
int centipede_initialize(GameData *game);
#endif // GAME_CENTIPEDE

2164
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

@ -38,10 +38,9 @@
#include "toxic_strings.h"
#include "windows.h"
extern char *DATA_FILE;
extern ToxWindow *prompt;
extern FriendsList Friends;
extern FriendRequests FrndRequests;
#ifdef GAMES
#include "game_base.h"
#endif
/* command functions */
void cmd_accept(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
@ -49,19 +48,19 @@ void cmd_accept(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
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;
}
@ -69,10 +68,10 @@ void cmd_accept(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
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.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Friend request accepted.");
on_friend_added(m, friendnum, true);
}
@ -141,7 +140,7 @@ void cmd_add_helper(ToxWindow *self, Tox *m, const char *id_bin, const char *msg
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])
@ -149,25 +148,19 @@ void cmd_add(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
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;
}
char msg[MAX_STR_SIZE] = {0};
const char *id = argv[1];
char msg[MAX_STR_SIZE];
const size_t arg_length = strlen(id);
const int space_idx = char_find(0, id, ' ');
if (argc > 1) {
if (argv[2][0] != '\"') {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Message must be enclosed in quotes.");
return;
}
/* remove opening and closing quotes */
char tmp[MAX_STR_SIZE];
snprintf(tmp, sizeof(tmp), "%s", &argv[2][1]);
int len = strlen(tmp) - 1;
tmp[len] = '\0';
snprintf(msg, sizeof(msg), "%s", tmp);
// we have to manually parse the message due to this command being a special case
if (space_idx > 0 && space_idx < arg_length - 1) {
snprintf(msg, sizeof(msg), "%s", &id[space_idx + 1]);
} else {
char selfname[TOX_MAX_NAME_LENGTH];
tox_self_get_name(m, (uint8_t *) selfname);
@ -178,36 +171,35 @@ void cmd_add(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
}
char id_bin[TOX_ADDRESS_SIZE] = {0};
uint16_t id_len = (uint16_t) strlen(id);
/* try to add tox ID */
if (id_len == 2 * TOX_ADDRESS_SIZE) {
size_t i;
char xx[3];
uint32_t x;
const bool is_tox_id = (char_find(0, id, '@') == arg_length) && (arg_length >= TOX_ADDRESS_SIZE * 2);
for (i = 0; i < TOX_ADDRESS_SIZE; ++i) {
xx[0] = id[2 * i];
xx[1] = id[2 * i + 1];
xx[2] = '\0';
if (!is_tox_id) {
name_lookup(self, m, id_bin, id, msg);
}
if (sscanf(xx, "%02x", &x) != 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid Tox ID.");
return;
}
char xx[3];
uint32_t x = 0;
id_bin[i] = x;
}
for (size_t i = 0; i < TOX_ADDRESS_SIZE; ++i) {
xx[0] = id[2 * i];
xx[1] = id[2 * i + 1];
xx[2] = 0;
if (friend_is_blocked(id_bin)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Friend is in your block list.");
if (sscanf(xx, "%02x", &x) != 1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid Tox ID.");
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);
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);
}
void cmd_avatar(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
@ -216,7 +208,7 @@ void cmd_avatar(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
if (argc != 1 || strlen(argv[1]) < 3) {
avatar_unset(m);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Avatar has been unset.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Avatar has been unset.");
return;
}
@ -225,7 +217,7 @@ void cmd_avatar(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
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;
}
@ -234,13 +226,13 @@ 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])
@ -258,7 +250,7 @@ void cmd_connect(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)
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;
}
@ -269,14 +261,14 @@ 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];
char key_binary[TOX_PUBLIC_KEY_SIZE];
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.");
if (tox_pk_string_to_bytes(ascii_key, strlen(ascii_key), key_binary, sizeof(key_binary)) == -1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid key.");
return;
}
@ -286,15 +278,15 @@ void cmd_connect(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)
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:
@ -308,19 +300,19 @@ void cmd_decline(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)
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;
}
@ -340,17 +332,74 @@ void cmd_decline(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)
--FrndRequests.num_requests;
}
#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, 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, 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;
}
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Please specify conference type: text | audio");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Please specify conference type: text | audio");
return;
}
@ -361,11 +410,11 @@ void cmd_conference(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar
} else if (!strcasecmp(argv[1], "text")) {
type = TOX_CONFERENCE_TYPE_TEXT;
} else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Valid conference types are: text | audio");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Valid conference types are: text | audio");
return;
}
uint32_t conferencenum;
uint32_t conferencenum = 0;
if (type == TOX_CONFERENCE_TYPE_TEXT) {
Tox_Err_Conference_New err;
@ -373,7 +422,7 @@ void cmd_conference(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar
conferencenum = tox_conference_new(m, &err);
if (err != TOX_ERR_CONFERENCE_NEW_OK) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference instance failed to initialize (error %d)", err);
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) {
@ -381,21 +430,18 @@ void cmd_conference(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar
conferencenum = toxav_add_av_groupchat(m, audio_conference_callback, NULL);
if (conferencenum == (uint32_t) -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio conference instance failed to initialize");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Audio conference instance failed to initialize");
return;
}
#else
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio support disabled by compile-time option.");
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, NULL, NULL, NULL, SYS_MSG, 0, 0, "Unknown conference type %d", type);
return;
}
if (init_conference_win(m, conferencenum, type, NULL, 0) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference window failed to initialize.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Conference window failed to initialize.");
tox_conference_delete(m, conferencenum, NULL);
return;
}
@ -404,13 +450,13 @@ void cmd_conference(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar
if (type == TOX_CONFERENCE_TYPE_AV) {
if (!init_conference_audio_input(m, conferencenum)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Audio capture failed; use \"/audio on\" to try again.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Audio capture failed; use \"/audio on\" to try again.");
}
}
#endif
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Conference [%d] created.", conferencenum);
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])
@ -427,29 +473,15 @@ void cmd_log(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
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->type == WINDOW_TYPE_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->type == WINDOW_TYPE_PROMPT) {
log_ret = log_enable(self->name, myid, NULL, log, LOG_PROMPT);
} else if (self->type == WINDOW_TYPE_CONFERENCE) {
log_ret = log_enable(self->name, myid, NULL, log, LOG_CONFERENCE);
}
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->type == WINDOW_TYPE_CHAT) {
@ -459,12 +491,12 @@ void cmd_log(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
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])
@ -477,12 +509,12 @@ void cmd_myid(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
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.");
if (tox_id_bytes_to_str(bin_id, sizeof(bin_id), id_string, sizeof(id_string)) == -1) {
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
@ -494,8 +526,8 @@ void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
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.");
if (tox_id_bytes_to_str(bin_id, sizeof(bin_id), id_string, sizeof(id_string)) == -1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code.");
return;
}
@ -508,7 +540,7 @@ void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
char *dir = malloc(data_file_len + 1);
if (dir == NULL) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code: Out of memory.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code: Out of memory.");
return;
}
@ -517,7 +549,7 @@ void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
#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")) {
@ -527,7 +559,7 @@ void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
char *qr_path = malloc(qr_path_buf_size);
if (qr_path == NULL) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code: Out of memory");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code: Out of memory");
free(dir);
return;
}
@ -535,13 +567,13 @@ void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
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, 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.");
free(dir);
free(qr_path);
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "QR code has been printed to the file '%s'", qr_path);
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);
@ -551,7 +583,7 @@ void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
char *qr_path = malloc(qr_path_buf_size);
if (qr_path == NULL) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code: Out of memory");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to create QR code: Out of memory");
free(dir);
return;
}
@ -559,18 +591,18 @@ void cmd_myqr(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
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, 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.");
free(dir);
free(qr_path);
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "QR code has been printed to the file '%s'", qr_path);
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;
}
@ -586,7 +618,7 @@ void cmd_nick(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
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;
}
@ -595,7 +627,7 @@ void cmd_nick(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
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;
}
@ -612,12 +644,9 @@ void cmd_note(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
{
UNUSED_VAR(window);
if (argc < 1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Input required.");
return;
}
const char *note = argc >= 1 ? argv[1] : "";
prompt_update_statusmessage(prompt, m, argv[1]);
prompt_update_statusmessage(prompt, m, note);
}
void cmd_nospam(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
@ -628,7 +657,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;
}
}
@ -636,12 +665,12 @@ 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);
}
@ -673,7 +702,7 @@ void cmd_requests(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv
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;
}
@ -693,11 +722,11 @@ 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, "");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "");
}
}
}
@ -712,7 +741,7 @@ void cmd_status(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
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;
}
@ -727,13 +756,13 @@ void cmd_status(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[
status = TOX_USER_STATUS_BUSY;
} 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, NULL, NULL, NULL, SYS_MSG, 0, 0, "Your status has been changed to %s.", status_str);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Your status has been changed to %s.", status_str);
finish:

View File

@ -62,4 +62,8 @@ void cmd_change_video_device(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv
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

@ -174,13 +174,17 @@ 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, " /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");
@ -264,6 +268,10 @@ static void help_draw_chat(ToxWindow *self)
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);
@ -306,7 +314,7 @@ static void help_draw_conference(ToxWindow *self)
wprintw(win, "Conference commands:\n");
wattroff(win, A_BOLD | COLOR_PAIR(RED));
wprintw(win, " /title <msg> : Set conference title (show current title if no msg)\n");
wprintw(win, " /title <msg> : Show/set conference title\n");
#ifdef AUDIO
wattron(win, A_BOLD);
wprintw(win, "\n Audio:\n");
@ -314,6 +322,7 @@ static void help_draw_conference(ToxWindow *self)
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
@ -377,11 +386,11 @@ void help_onKey(ToxWindow *self, wint_t key)
case L'c':
#ifdef VIDEO
help_init_window(self, 25, 80);
help_init_window(self, 26, 80);
#elif AUDIO
help_init_window(self, 20, 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;
@ -395,6 +404,9 @@ void help_onKey(ToxWindow *self, wint_t key)
#endif
#ifdef PYTHON
height += 2;
#endif
#ifdef GAMES
height += 1;
#endif
help_init_window(self, height, 80);
self->help->type = HELP_GLOBAL;
@ -403,7 +415,7 @@ void help_onKey(ToxWindow *self, wint_t key)
case L'o':
height = 6;
#ifdef AUDIO
height += 5;
height += 7;
#endif
help_init_window(self, height, 80);
self->help->type = HELP_CONFERENCE;

View File

@ -346,5 +346,9 @@ bool input_handle(ToxWindow *self, wint_t key, int x, int mx_x)
}
}
if (match) {
flag_interface_refresh();
}
return match;
}

View File

@ -46,7 +46,7 @@ void line_info_init(struct history *hst)
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) */
@ -54,27 +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->type == WINDOW_TYPE_CHAT) || (self->type == WINDOW_TYPE_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)
@ -87,9 +88,7 @@ void line_info_cleanup(struct history *hst)
tmp1 = tmp2;
}
int i;
for (i = 0; i < hst->queue_sz; ++i) {
for (size_t i = 0; i < hst->queue_size; ++i) {
if (hst->queue[i]) {
free(hst->queue[i]);
}
@ -117,29 +116,231 @@ 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;
}
/* 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)
{
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;
getyx(win, y, x);
UNUSED_VAR(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;
getmaxyx(self->window, y2, x2);
UNUSED_VAR(y2);
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, const char *timestr, const char *name1, const char *name2, uint8_t type,
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) {
@ -148,7 +349,7 @@ int line_info_add(ToxWindow *self, const char *timestr, const char *name1, const
struct history *hst = self->chatwin->hst;
if (hst->queue_sz >= MAX_LINE_INFO_QUEUE) {
if (hst->queue_size >= MAX_LINE_INFO_QUEUE) {
return -1;
}
@ -158,7 +359,8 @@ int line_info_add(ToxWindow *self, const char *timestr, const char *name1, const
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);
@ -173,33 +375,33 @@ int line_info_add(ToxWindow *self, const char *timestr, const char *name1, const
/* 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:
@ -207,21 +409,16 @@ int line_info_add(ToxWindow *self, const char *timestr, const char *name1, const
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;
}
@ -235,15 +432,22 @@ int line_info_add(ToxWindow *self, const char *timestr, const char *name1, const
len += strlen(new_line->name2);
}
new_line->id = (hst->line_end->id + 1 + hst->queue_sz) % INT_MAX;
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;
if (type == OUT_MSG || type == OUT_ACTION) {
new_line->noread_flag = self->stb->connection == TOX_CONNECTION_NONE;
}
line_info_init_line(self, new_line);
hst->queue[hst->queue_size++] = new_line;
return new_line->id;
}
@ -267,32 +471,11 @@ static void line_info_check_queue(ToxWindow *self)
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);
UNUSED_VAR(x);
if (x2 <= SIDEBAR_WIDTH) {
return;
}
int offst = self->show_peerlist ? SIDEBAR_WIDTH : 0; /* offset width of conference 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);
}
}
#define NOREAD_FLAG_TIMEOUT 5 /* seconds before a sent message with no read receipt is flagged as unread */
void line_info_print(ToxWindow *self)
{
ChatContext *ctx = self->chatwin;
@ -310,25 +493,44 @@ void line_info_print(ToxWindow *self)
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->type == WINDOW_TYPE_CONFERENCE) {
wmove(win, 0, 0);
} else {
wmove(win, 2, 0);
wmove(win, TOP_BAR_HEIGHT, 0);
}
struct line_info *line = hst->line_start->next;
int numlines = 0;
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;
getyx(win, y, x);
UNUSED_VAR(y);
if (x > 0) { // Prevents us from printing off the screen
break;
}
while (line && numlines++ <= y2) {
uint8_t type = line->type;
switch (type) {
@ -355,43 +557,26 @@ void line_info_print(ToxWindow *self)
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");
wattroff(win, COLOR_PAIR(RED));
if (line->noread_flag == false) {
line->noread_flag = true;
line->len += 2;
}
}
wprintw(win, "\n");
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));
}
waddch(win, '\n');
break;
case OUT_ACTION_READ:
@ -406,21 +591,11 @@ 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");
wattroff(win, COLOR_PAIR(RED));
if (line->noread_flag == false) {
line->noread_flag = true;
line->len += 2;
}
}
wprintw(win, "\n");
waddch(win, '\n');
break;
case SYS_MSG:
@ -438,7 +613,8 @@ void line_info_print(ToxWindow *self)
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) {
wattroff(win, A_BOLD);
@ -456,10 +632,10 @@ void line_info_print(ToxWindow *self)
wattroff(win, COLOR_PAIR(GREEN));
if (line->msg[0]) {
wprintw(win, "%s", line->msg);
print_ret = print_wrap(win, line, max_x, max_y);
}
wprintw(win, "\n");
waddch(win, '\n');
break;
case CONNECTION:
@ -474,7 +650,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;
@ -491,7 +669,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;
@ -507,7 +687,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);
@ -520,19 +700,58 @@ void line_info_print(ToxWindow *self)
line = line->next;
}
flag_interface_refresh();
/* 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 */
void line_info_set(ToxWindow *self, uint32_t id, char *msg)
{
flag_interface_refresh();
struct line_info *line = self->chatwin->hst->line_end;
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;
}
@ -541,56 +760,92 @@ void line_info_set(ToxWindow *self, uint32_t id, char *msg)
}
}
/* static void line_info_goto_root(struct history *hst)
/* Return the line_info object associated with `id`.
* Return NULL if id cannot be found
*/
struct line_info *line_info_get(ToxWindow *self, uint32_t id)
{
hst->line_start = hst->line_root;
} */
struct line_info *line = self->chatwin->hst->line_end;
static void line_info_scroll_up(struct history *hst)
while (line) {
if (line->id == id) {
return line;
}
line = line->prev;
}
return NULL;
}
static void line_info_scroll_up(ToxWindow *self, struct history *hst)
{
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;
}
}
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;
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 {
sound_notify(NULL, notif_error, NT_ALWAYS, NULL);
line_info_reset_start(self, hst);
}
}
static void line_info_page_up(ToxWindow *self, struct history *hst)
{
int x2, y2;
int x2;
int y2;
getmaxyx(self->window, y2, x2);
UNUSED_VAR(x2);
int jump_dist = y2 / 2;
int i;
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 (i = 0; i < jump_dist && hst->line_start->prev; ++i) {
for (size_t i = 0; i < jump_dist && hst->line_start->prev; ++i) {
hst->line_start = hst->line_start->prev;
}
self->scroll_pause = true;
}
static void line_info_page_down(ToxWindow *self, struct history *hst)
{
int x2, y2;
getmaxyx(self->window, y2, x2);
if (!self->scroll_pause) {
return;
}
int x2;
int y2;
getmaxyx(self->chatwin->history, y2, x2);
UNUSED_VAR(x2);
int jump_dist = y2 / 2;
int i;
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 (i = 0; i < jump_dist && hst->line_start->next; ++i) {
hst->line_start = hst->line_start->next;
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;
}
}
@ -604,15 +859,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_refresh();
}
return match;
}

View File

@ -29,9 +29,9 @@
#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 */
typedef 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,7 +73,7 @@ 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.
@ -79,7 +81,7 @@ struct history {
* Returns the id of the new line.
* Returns -1 on failure.
*/
int line_info_add(ToxWindow *self, const char *timestr, const char *name1, const char *name2, uint8_t type,
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 */
@ -94,6 +96,11 @@ void line_info_clear(struct history *hst);
/* puts msg in specified line_info msg buffer */
void line_info_set(ToxWindow *self, uint32_t id, char *msg);
/* Return the line_info object associated with `id`.
* Return NULL if id cannot be found
*/
struct line_info *line_info_get(ToxWindow *self, uint32_t id);
/* resets line_start (moves to end of chat history) */
void line_info_reset_start(ToxWindow *self, struct history *hst);

256
src/log.c
View File

@ -35,29 +35,33 @@
extern struct user_settings *user_settings;
/* There are three types of logs: chat logs, conference 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 conference log is in the format: LOGDIR/selfkey-conferencename-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)) {
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);
@ -66,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_CONFERENCE:
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) {
@ -94,28 +92,35 @@ static int get_log_path(char *dest, int destsize, char *name, const char *selfke
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) {
int path_len = get_log_path(log_path, sizeof(log_path), name, selfkey, otherkey);
if (path_len == -1 || path_len >= sizeof(log->path)) {
return -1;
}
log->file = fopen(log_path, "a+");
snprintf(log->path, sizeof(log->path), "%s", log_path);
if (log->file == NULL) {
return -1;
}
memcpy(log->path, log_path, path_len);
log->path[path_len] = 0;
return 0;
}
@ -124,6 +129,10 @@ 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 == NULL) {
return;
}
if (!log->log_on) {
return;
}
@ -154,94 +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;
}
*log = (struct chatlog) {
0
};
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 == NULL) {
return -1;
}
if (log->file != NULL) {
if (log->log_on) {
return 0;
}
if (init_logging_session(name, selfkey, otherkey, log, logtype) == -1) {
log_disable(log);
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;
return 0;
}
char *hstbuf = malloc(sz + 1);
FILE *fp = fopen(log->path, "r");
if (hstbuf == NULL) {
exit_toxic_err("failed in load_chat_history", FATALERR_MEMORY);
if (fp == NULL) {
return -1;
}
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;
char *buf = malloc(sz + 1);
if (buf == NULL) {
fclose(fp);
return -1;
}
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;
if (fseek(fp, 0L, SEEK_SET) == -1) {
free(buf);
fclose(fp);
return -1;
}
hstbuf[sz] = '\0';
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;
@ -250,6 +327,11 @@ 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;
}
@ -260,24 +342,36 @@ int rename_logfile(char *src, char *dest, const char *selfkey, const char *other
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)) {
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;
@ -285,7 +379,7 @@ int rename_logfile(char *src, char *dest, const char *selfkey, const char *other
on_error:
if (log_on) {
log_enable(src, selfkey, otherkey, log, LOG_CHAT);
log_enable(log);
}
return -1;

View File

@ -30,30 +30,43 @@ struct chatlog {
bool log_on; /* specific to current chat window */
};
typedef enum {
LOG_CONFERENCE,
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 /* LOG_H */

View File

@ -59,6 +59,7 @@ void cqueue_add(struct chat_queue *q, const char *msg, size_t len, uint8_t type,
new_m->type = type;
new_m->line_id = line_id;
new_m->last_send_try = 0;
new_m->time_added = get_unix_time();
new_m->receipt = -1;
new_m->next = NULL;
@ -76,23 +77,19 @@ void cqueue_add(struct chat_queue *q, const char *msg, size_t len, uint8_t type,
/* update line to show receipt was received after queue removal */
static void cqueue_mark_read(ToxWindow *self, struct cqueue_msg *msg)
{
struct line_info *line = self->chatwin->hst->line_end;
while (line) {
if (line->id != msg->line_id) {
line = line->prev;
continue;
}
line->type = msg->type == OUT_ACTION ? OUT_ACTION_READ : OUT_MSG_READ;
if (line->noread_flag == true) {
line->len -= 2;
line->noread_flag = false;
}
struct line_info *line = line_info_get(self, msg->line_id);
if (line == NULL) {
return;
}
line->type = msg->type == OUT_ACTION ? OUT_ACTION_READ : OUT_MSG_READ;
if (line->noread_flag) {
line->noread_flag = false;
line->read_flag = true;
flag_interface_refresh();
}
}
/* removes message with matching receipt from queue, writes to log and updates line to show the message was received. */
@ -158,6 +155,36 @@ static void cqueue_check_timeouts(struct cqueue_msg *msg)
}
}
/*
* Sets the noread flag for messages sent to the peer associated with `self` which have not
* received a receipt after a period of time.
*/
#define NOREAD_TIMEOUT 5
void cqueue_check_unread(ToxWindow *self)
{
struct chat_queue *q = self->chatwin->cqueue;
struct cqueue_msg *msg = q->root;
while (msg) {
if (msg->noread_flag) {
msg = msg->next;
continue;
}
struct line_info *line = line_info_get(self, msg->line_id);
if (line != NULL) {
if (timed_out(msg->time_added, NOREAD_TIMEOUT)) {
line->noread_flag = true;
msg->noread_flag = true;
flag_interface_refresh();
}
}
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.

View File

@ -28,8 +28,10 @@ struct cqueue_msg {
size_t len;
int line_id;
time_t last_send_try;
time_t time_added;
uint8_t type;
int64_t receipt;
bool noread_flag;
struct cqueue_msg *next;
struct cqueue_msg *prev;
};
@ -48,6 +50,12 @@ void cqueue_add(struct chat_queue *q, const char *msg, size_t len, uint8_t type,
*/
void cqueue_try_send(ToxWindow *self, Tox *m);
/*
* Sets the noread flag for messages sent to the peer associated with `self` which have not
* received a receipt after a period of time.
*/
void cqueue_check_unread(ToxWindow *self);
/* 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);

View File

@ -71,16 +71,19 @@ int timed_out(time_t timestamp, time_t timeout)
return timestamp + timeout <= get_unix_time();
}
/* Sleeps the caller's thread for `usec` microseconds */
/* 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, NULL) == -1) {
fprintf(stderr, "nanosleep() returned -1\n");
if (nanosleep(&req, &rem) == -1) {
if (nanosleep(&rem, NULL) == -1) {
fprintf(stderr, "nanosleep() returned -1\n");
}
}
}
@ -93,11 +96,16 @@ 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;
}
@ -129,15 +137,18 @@ void get_elapsed_time_str(char *buf, int bufsize, time_t secs)
}
/*
* Converts a hexidecimal string of length hex_len to binary format and puts the result in output.
* output_size must be exactly half of hex_len.
* Converts a hexidecimal string representation of a Tox public key to binary format and puts
* the result in output.
*
* `hex_len` must be exactly TOX_PUBLIC_KEY_SIZE * 2, and `output_size` must have room
* for TOX_PUBLIC_KEY_SIZE bytes.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int hex_string_to_bin(const char *hex_string, size_t hex_len, char *output, size_t output_size)
int tox_pk_string_to_bytes(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 != TOX_PUBLIC_KEY_SIZE || hex_len != output_size * 2) {
return -1;
}
@ -149,6 +160,11 @@ int hex_string_to_bin(const char *hex_string, size_t hex_len, char *output, size
return 0;
}
/* Convert a hexadecimcal string of length `size` to bytes and puts the result in `keystr`.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int hex_string_to_bytes(char *buf, int size, const char *keystr)
{
if (size % 2 != 0) {
@ -170,11 +186,14 @@ int hex_string_to_bytes(char *buf, int size, const char *keystr)
}
/* Converts a binary representation of a Tox ID into a string.
*
* `bin_id_size` must be exactly TOX_ADDRESS_SIZE bytes in length, and
* `output_size` must be at least TOX_ADDRESS_SIZE * 2 + 1.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int bin_id_to_string(const char *bin_id, size_t bin_id_size, char *output, size_t output_size)
int tox_id_bytes_to_str(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)) {
return -1;
@ -188,11 +207,14 @@ int bin_id_to_string(const char *bin_id, size_t bin_id_size, char *output, size_
}
/* Converts a binary representation of a Tox public key into a string.
*
* `bin_pubkey_size` must be exactly TOX_PUBLIC_KEY_SIZE bytes in size, and
* `output_size` must be at least TOX_PUBLIC_KEY_SIZE * 2 + 1.
*
* 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)
int tox_pk_bytes_to_str(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;
@ -266,7 +288,7 @@ int qsort_strcasecmp_hlpr(const void *str1, const void *str2)
/* case-insensitive string compare function for use with qsort */
int qsort_ptr_char_array_helper(const void *str1, const void *str2)
{
return strcasecmp(*(char **)str1, *(char **)str2);
return strcasecmp(*(const char *const *)str1, *(const char *const *)str2);
}
static const char invalid_chars[] = {'/', '\n', '\t', '\v', '\r', '\0'};
@ -584,7 +606,7 @@ void set_window_title(ToxWindow *self, const char *title, int len)
char cpy[TOXIC_MAX_NAME_LENGTH + 1];
if (self->type == WINDOW_TYPE_CONFERENCE) { /* keep conferencenumber in title */
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);

View File

@ -53,36 +53,49 @@ void clear_screen(void);
void hst_to_net(uint8_t *num, uint16_t numbytes);
/*
* Converts a hexidecimal string of length hex_len to binary format and puts the result in output.
* output_size must be exactly half of hex_len.
* Converts a hexidecimal string representation of a Tox public key to binary format and puts
* the result in output.
*
* `hex_len` must be exactly TOX_PUBLIC_KEY_SIZE * 2, and `output_size` must have room
* for TOX_PUBLIC_KEY_SIZE bytes.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int hex_string_to_bin(const char *hex_string, size_t hex_len, char *output, size_t output_size);
int tox_pk_string_to_bytes(const char *hex_string, size_t hex_len, char *output, size_t output_size);
/* convert a hex string to bytes. returns 0 on success, -1 on failure */
/* Converts a binary representation of a Tox public key into a string.
*
* `bin_pubkey_size` must be exactly TOX_PUBLIC_KEY_SIZE bytes in size, and
* `output_size` must be at least TOX_PUBLIC_KEY_SIZE * 2 + 1.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int tox_pk_bytes_to_str(const uint8_t *bin_pubkey, size_t bin_pubkey_size, char *output, size_t output_size);
/* Convert a hexadecimcal string of length `size` to bytes and puts the result in `keystr`.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
int hex_string_to_bytes(char *buf, int size, const char *keystr);
/* Converts a binary representation of a Tox ID into a string.
*
* Returns 0 on success.
* Returns -1 on failure.
*/
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.
* `bin_id_size` must be exactly TOX_ADDRESS_SIZE bytes in length, and
* `output_size` must be at least TOX_ADDRESS_SIZE * 2 + 1.
*
* 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);
int tox_id_bytes_to_str(const char *bin_id, size_t bin_id_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);
@ -108,7 +121,7 @@ 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);
/* Sleeps the caller's thread for `usec` microseconds */
/* 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 */

View File

@ -33,9 +33,6 @@
#include "toxic.h"
#include "windows.h"
extern struct arg_opts arg_opts;
extern struct Winthread Winthread;
#define NAMESERVER_API_PATH "api"
#define SERVER_KEY_SIZE 32
#define MAX_SERVERS 50
@ -80,7 +77,7 @@ static int lookup_error(ToxWindow *self, const char *errmsg, ...)
va_end(args);
pthread_mutex_lock(&Winthread.lock);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "name lookup failed: %s", frmt_msg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "name lookup failed: %s", frmt_msg);
pthread_mutex_unlock(&Winthread.lock);
return -1;
@ -111,14 +108,15 @@ static int load_nameserver_list(const char *path)
char line[MAX_SERVER_LINE];
while (fgets(line, sizeof(line), fp) && Nameservers.lines < MAX_SERVERS) {
int linelen = strlen(line);
size_t linelen = strlen(line);
if (linelen < SERVER_KEY_SIZE * 2 + 5) {
continue;
}
if (line[linelen - 1] == '\n') {
line[--linelen] = '\0';
--linelen;
line[linelen] = '\0';
}
const char *name = strtok(line, " ");
@ -132,8 +130,10 @@ static int load_nameserver_list(const char *path)
continue;
}
snprintf(Nameservers.names[Nameservers.lines], sizeof(Nameservers.names[Nameservers.lines]), "%s", name);
int res = hex_string_to_bytes(Nameservers.keys[Nameservers.lines], SERVER_KEY_SIZE, keystr);
const size_t idx = Nameservers.lines;
snprintf(Nameservers.names[idx], sizeof(Nameservers.names[idx]), "%s", name);
int res = hex_string_to_bytes(Nameservers.keys[idx], SERVER_KEY_SIZE, keystr);
if (res == -1) {
continue;
@ -231,7 +231,7 @@ static int process_response(struct Recv_Curl_Data *recv_data)
memcpy(ID_string, IDstart + prefix_size, TOX_ADDRESS_SIZE * 2);
ID_string[TOX_ADDRESS_SIZE * 2] = 0;
if (hex_string_to_bin(ID_string, strlen(ID_string), t_data.id_bin, sizeof(t_data.id_bin)) == -1) {
if (tox_pk_string_to_bytes(ID_string, strlen(ID_string), t_data.id_bin, sizeof(t_data.id_bin)) == -1) {
return -1;
}
@ -365,12 +365,12 @@ on_exit:
void name_lookup(ToxWindow *self, Tox *m, const char *id_bin, const char *addr, const char *message)
{
if (t_data.disabled) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "name lookups are disabled.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "name lookups are disabled.");
return;
}
if (t_data.busy) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Please wait for previous name lookup to finish.");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Please wait for previous name lookup to finish.");
return;
}
@ -382,20 +382,20 @@ void name_lookup(ToxWindow *self, Tox *m, const char *id_bin, const char *addr,
t_data.busy = true;
if (pthread_attr_init(&lookup_thread.attr) != 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, "Error: lookup thread attr failed to init");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, "Error: lookup thread attr failed to init");
clear_thread_data();
return;
}
if (pthread_attr_setdetachstate(&lookup_thread.attr, PTHREAD_CREATE_DETACHED) != 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, "Error: lookup thread attr failed to set");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, "Error: lookup thread attr failed to set");
pthread_attr_destroy(&lookup_thread.attr);
clear_thread_data();
return;
}
if (pthread_create(&lookup_thread.tid, &lookup_thread.attr, lookup_thread_func, NULL) != 0) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, "Error: lookup thread failed to init");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, "Error: lookup thread failed to init");
pthread_attr_destroy(&lookup_thread.attr);
clear_thread_data();
return;

View File

@ -35,7 +35,7 @@
#include "misc_tools.h"
#include "notify.h"
#include "settings.h"
#include "xtra.h"
#include "x11focus.h"
#if defined(AUDIO) || defined(SOUND_NOTIFY)
#ifdef __APPLE__
@ -126,6 +126,8 @@ static void tab_notify(ToxWindow *self, uint64_t flags)
} else if ((flags & NT_WNDALERT_2) && (!self->alert || self->alert > WINDOW_ALERT_1)) {
self->alert = WINDOW_ALERT_2;
}
++self->pending_messages;
}
static bool notifications_are_disabled(uint64_t flags)
@ -316,7 +318,7 @@ void *do_playing(void *_p)
has_looping = false;
control_unlock();
sleep_thread(10000L);
sleep_thread(100000L);
}
pthread_exit(NULL);
@ -487,16 +489,27 @@ void terminate_notify(void)
}
#ifdef SOUND_NOTIFY
int set_sound(Notification sound, const char *value)
/*
* Sets notification sound designated by `sound` to file path `value`.
*
* Return true if the sound is successfully set.
*/
bool set_sound(Notification sound, const char *value)
{
if (sound == silent) {
return 0;
return false;
}
free(Control.sounds[sound]);
size_t len = strlen(value) + 1;
Control.sounds[sound] = calloc(len, 1);
if (Control.sounds[sound] == NULL) {
return false;
}
memcpy(Control.sounds[sound], value, len);
struct stat buf;
@ -705,6 +718,7 @@ int box_notify(ToxWindow *self, Notification notif, uint64_t flags, int *id_indi
#else
if (id == -1) {
control_unlock();
return -1;
}

View File

@ -77,7 +77,7 @@ int box_silent_notify(ToxWindow *self, uint64_t flags, int *id_indicator, const
int box_silent_notify2(ToxWindow *self, uint64_t flags, int id, const char *format, ...);
#ifdef SOUND_NOTIFY
int set_sound(Notification sound, const char *value);
bool set_sound(Notification sound, const char *value);
#endif /* SOUND_NOTIFY */
#endif /* NOTIFY_H */

View File

@ -60,6 +60,9 @@ static const char *glob_cmd_list[] = {
"/decline",
"/exit",
"/conference",
#ifdef GAMES
"/game",
#endif
"/help",
"/log",
"/myid",
@ -122,6 +125,8 @@ void on_self_connection_status(Tox *m, Tox_Connection connection_status, void *u
UNUSED_VAR(userdata);
StatusBar *statusbar = prompt->stb;
statusbar->connection = connection_status;
flag_interface_refresh();
}
/* Updates own nick in prompt statusbar */
@ -144,7 +149,7 @@ void prompt_update_statusmessage(ToxWindow *prompt, Tox *m, const char *statusms
tox_self_set_status_message(m, (const uint8_t *) statusmsg, len, &err);
if (err != TOX_ERR_SET_INFO_OK) {
line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set note (error %d)\n", err);
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to set note (error %d)\n", err);
}
}
@ -281,18 +286,21 @@ static bool prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
add_line_to_hist(ctx);
wstrsubst(ctx->line, L'', L'\n');
char line[MAX_STR_SIZE] = {0};
char line[MAX_STR_SIZE];
if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) {
memset(line, 0, sizeof(line));
}
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Failed to parse message.");
} else {
if (strcmp(line, "/clear") != 0) {
line_info_add(self, false, NULL, NULL, PROMPT, 0, 0, "%s", line);
}
line_info_add(self, NULL, NULL, NULL, PROMPT, 0, 0, "%s", line);
execute(ctx->history, self, m, line, GLOBAL_COMMAND_MODE);
execute(ctx->history, self, m, line, GLOBAL_COMMAND_MODE);
}
}
wclear(ctx->linewin);
wmove(self->window, y2 - CURS_Y_OFFSET, 0);
wmove(self->window, y2, 0);
reset_buf(ctx);
}
@ -301,7 +309,8 @@ static bool prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr)
static void prompt_onDraw(ToxWindow *self, Tox *m)
{
int x2, y2;
int x2;
int y2;
getmaxyx(self->window, y2, x2);
if (y2 <= 0 || x2 <= 0) {
@ -316,74 +325,113 @@ static void prompt_onDraw(ToxWindow *self, Tox *m)
wclear(ctx->linewin);
curs_set(1);
if (ctx->len > 0) {
mvwprintw(ctx->linewin, 1, 0, "%ls", &ctx->line[ctx->start]);
mvwprintw(ctx->linewin, 0, 0, "%ls", &ctx->line[ctx->start]);
}
curs_set(1);
StatusBar *statusbar = self->stb;
mvwhline(statusbar->topline, 1, 0, ACS_HLINE, x2);
wmove(statusbar->topline, 0, 0);
pthread_mutex_lock(&Winthread.lock);
Tox_Connection connection = statusbar->connection;
Tox_User_Status status = statusbar->status;
pthread_mutex_unlock(&Winthread.lock);
if (connection != TOX_CONNECTION_NONE) {
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, " [");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
switch (connection) {
case TOX_CONNECTION_TCP:
wattron(statusbar->topline, A_BOLD | COLOR_PAIR(STATUS_ONLINE));
wprintw(statusbar->topline, "TCP");
wattroff(statusbar->topline, A_BOLD | COLOR_PAIR(STATUS_ONLINE));
break;
case TOX_CONNECTION_UDP:
wattron(statusbar->topline, A_BOLD | COLOR_PAIR(STATUS_ONLINE));
wprintw(statusbar->topline, "UDP");
wattroff(statusbar->topline, A_BOLD | COLOR_PAIR(STATUS_ONLINE));
break;
default:
wattron(statusbar->topline, COLOR_PAIR(BAR_TEXT));
wprintw(statusbar->topline, "Offline");
wattroff(statusbar->topline, COLOR_PAIR(BAR_TEXT));
break;
}
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, "]");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
if (status != TOX_USER_STATUS_NONE) {
int colour = MAGENTA;
const char *status_text = "ERROR";
pthread_mutex_lock(&Winthread.lock);
Tox_User_Status status = statusbar->status;
pthread_mutex_unlock(&Winthread.lock);
switch (status) {
case TOX_USER_STATUS_NONE:
status_text = "Online";
colour = GREEN;
break;
case TOX_USER_STATUS_AWAY:
colour = STATUS_AWAY;
status_text = "Away";
colour = YELLOW;
break;
case TOX_USER_STATUS_BUSY:
colour = STATUS_BUSY;
status_text = "Busy";
colour = RED;
break;
default:
break;
}
wattron(statusbar->topline, COLOR_PAIR(colour) | A_BOLD);
wprintw(statusbar->topline, " [%s]", status_text);
wattroff(statusbar->topline, COLOR_PAIR(colour) | A_BOLD);
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, " [");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wattron(statusbar->topline, A_BOLD | COLOR_PAIR(colour));
wprintw(statusbar->topline, "%s", status_text);
wattroff(statusbar->topline, A_BOLD | COLOR_PAIR(colour));
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, "]");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wattron(statusbar->topline, COLOR_PAIR(BAR_TEXT));
wattron(statusbar->topline, A_BOLD);
pthread_mutex_lock(&Winthread.lock);
wprintw(statusbar->topline, " %s", statusbar->nick);
pthread_mutex_unlock(&Winthread.lock);
wattroff(statusbar->topline, A_BOLD);
} else {
wprintw(statusbar->topline, " [Offline]");
wattron(statusbar->topline, A_BOLD);
wattron(statusbar->topline, COLOR_PAIR(BAR_TEXT));
pthread_mutex_lock(&Winthread.lock);
wprintw(statusbar->topline, " %s", statusbar->nick);
pthread_mutex_unlock(&Winthread.lock);
wattroff(statusbar->topline, A_BOLD);
}
int s_y;
int s_x;
getyx(statusbar->topline, s_y, s_x);
mvwhline(statusbar->topline, s_y, s_x, ' ', x2 - s_x);
wattroff(statusbar->topline, COLOR_PAIR(BAR_TEXT));
/* Reset statusbar->statusmsg on window resize */
if (x2 != self->x) {
char statusmsg[TOX_MAX_STATUS_MESSAGE_LENGTH];
pthread_mutex_lock(&Winthread.lock);
size_t slen = tox_self_get_status_message_size(m);
tox_self_get_status_message(m, (uint8_t *) statusmsg);
statusmsg[slen] = '\0';
snprintf(statusbar->statusmsg, sizeof(statusbar->statusmsg), "%s", statusmsg);
statusbar->statusmsg_len = strlen(statusbar->statusmsg);
pthread_mutex_unlock(&Winthread.lock);
}
@ -393,28 +441,39 @@ static void prompt_onDraw(ToxWindow *self, Tox *m)
uint16_t maxlen = x2 - getcurx(statusbar->topline) - 3;
pthread_mutex_lock(&Winthread.lock);
if (statusbar->statusmsg_len > maxlen) {
statusbar->statusmsg[maxlen - 3] = '\0';
strcat(statusbar->statusmsg, "...");
statusbar->statusmsg_len = maxlen;
}
if (statusbar->statusmsg[0]) {
wprintw(statusbar->topline, " : %s", statusbar->statusmsg);
}
size_t statusmsg_len = statusbar->statusmsg_len;
pthread_mutex_unlock(&Winthread.lock);
mvwhline(self->window, y2 - CHATBOX_HEIGHT, 0, ACS_HLINE, x2);
if (statusmsg_len > maxlen) {
pthread_mutex_lock(&Winthread.lock);
statusbar->statusmsg[maxlen - 3] = 0;
strcat(statusbar->statusmsg, "...");
statusbar->statusmsg_len = maxlen;
pthread_mutex_unlock(&Winthread.lock);
}
int y, x;
if (statusmsg_len) {
wattron(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wprintw(statusbar->topline, " | ");
wattroff(statusbar->topline, COLOR_PAIR(BAR_ACCENT));
wattron(statusbar->topline, COLOR_PAIR(BAR_TEXT));
pthread_mutex_lock(&Winthread.lock);
wprintw(statusbar->topline, "%s", statusbar->statusmsg);
pthread_mutex_unlock(&Winthread.lock);
wattroff(statusbar->topline, COLOR_PAIR(BAR_TEXT));
}
int y;
int x;
getyx(self->window, y, x);
UNUSED_VAR(x);
int new_x = ctx->start ? x2 - 1 : MAX(0, wcswidth(ctx->line, ctx->pos));
wmove(self->window, y + 1, new_x);
wmove(self->window, y, new_x);
draw_window_bar(self);
wnoutrefresh(self->window);
@ -434,8 +493,6 @@ static void prompt_onConnectionChange(ToxWindow *self, Tox *m, uint32_t friendnu
snprintf(nick, sizeof(nick), "%s", UNKNOWN_NAME);
}
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
const char *msg;
if (user_settings->show_connection_msg == SHOW_WELCOME_MSG_OFF) {
@ -444,7 +501,7 @@ static void prompt_onConnectionChange(ToxWindow *self, Tox *m, uint32_t friendnu
if (connection_status != TOX_CONNECTION_NONE && Friends.list[friendnum].connection_status == TOX_CONNECTION_NONE) {
msg = "has come online";
line_info_add(self, timefrmt, nick, NULL, CONNECTION, 0, GREEN, msg);
line_info_add(self, true, nick, NULL, CONNECTION, 0, GREEN, msg);
write_to_log(msg, nick, ctx->log, true);
if (self->active_box != -1) {
@ -456,7 +513,7 @@ static void prompt_onConnectionChange(ToxWindow *self, Tox *m, uint32_t friendnu
}
} else if (connection_status == TOX_CONNECTION_NONE) {
msg = "has gone offline";
line_info_add(self, timefrmt, nick, NULL, DISCONNECTION, 0, RED, msg);
line_info_add(self, true, nick, NULL, DISCONNECTION, 0, RED, msg);
write_to_log(msg, nick, ctx->log, true);
if (self->active_box != -1) {
@ -476,21 +533,18 @@ static void prompt_onFriendRequest(ToxWindow *self, Tox *m, const char *key, con
ChatContext *ctx = self->chatwin;
char timefrmt[TIME_STR_SIZE];
get_time_str(timefrmt, sizeof(timefrmt));
line_info_add(self, timefrmt, NULL, NULL, SYS_MSG, 0, 0, "Friend request with the message '%s'", data);
line_info_add(self, true, NULL, NULL, SYS_MSG, 0, 0, "Friend request with the message '%s'", data);
write_to_log("Friend request with the message '%s'", "", ctx->log, true);
int n = add_friend_request(key, data);
if (n == -1) {
const char *errmsg = "Friend request queue is full. Discarding request.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, errmsg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, errmsg);
return;
}
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Type \"/accept %d\" or \"/decline %d\"", n, n);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Type \"/accept %d\" or \"/decline %d\"", n, n);
sound_notify(self, generic_message, NT_WNDALERT_1 | NT_NOTIFWND, NULL);
}
@ -535,29 +589,50 @@ void prompt_init_statusbar(ToxWindow *self, Tox *m, bool first_time_run)
prompt_update_nick(prompt, nick);
/* Init statusbar subwindow */
statusbar->topline = subwin(self->window, 2, x2, 0, 0);
statusbar->topline = subwin(self->window, TOP_BAR_HEIGHT, x2, 0, 0);
}
static void print_welcome_msg(ToxWindow *self)
{
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 1, BLUE, " _____ _____ _____ ____ ");
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 1, BLUE, " |_ _/ _ \\ \\/ /_ _/ ___|");
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 1, BLUE, " | || | | \\ / | | | ");
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 1, BLUE, " | || |_| / \\ | | |___ ");
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 1, BLUE, " |_| \\___/_/\\_\\___\\____| v." TOXICVER);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "");
line_info_add(self, false, NULL, NULL, SYS_MSG, 1, BLUE, " _____ _____ _____ ____ ");
line_info_add(self, false, NULL, NULL, SYS_MSG, 1, BLUE, " |_ _/ _ \\ \\/ /_ _/ ___|");
line_info_add(self, false, NULL, NULL, SYS_MSG, 1, BLUE, " | || | | \\ / | | | ");
line_info_add(self, false, NULL, NULL, SYS_MSG, 1, BLUE, " | || |_| / \\ | | |___ ");
line_info_add(self, false, NULL, NULL, SYS_MSG, 1, BLUE, " |_| \\___/_/\\_\\___\\____| v." TOXICVER);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "");
const char *msg = "Welcome to Toxic, a free, open source Tox-based instant messaging client.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 1, CYAN, msg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 1, CYAN, msg);
msg = "Type \"/help\" for assistance. Further help may be found via the man page.";
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 1, CYAN, msg);
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "");
line_info_add(self, false, NULL, NULL, SYS_MSG, 1, CYAN, msg);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "");
}
static void prompt_init_log(ToxWindow *self, Tox *m, const char *self_name)
{
ChatContext *ctx = self->chatwin;
char myid[TOX_ADDRESS_SIZE];
tox_self_get_address(m, (uint8_t *) myid);
if (log_init(ctx->log, self->name, myid, NULL, LOG_TYPE_PROMPT) != 0) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Warning: Log failed to initialize.");
return;
}
if (user_settings->autolog == AUTOLOG_ON) {
if (log_enable(ctx->log) == -1) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Warning: Failed to enable log.");
}
}
}
static void prompt_onInit(ToxWindow *self, Tox *m)
{
curs_set(1);
int y2, x2;
int y2;
int x2;
getmaxyx(self->window, y2, x2);
if (y2 <= 0 || x2 <= 0) {
@ -565,8 +640,10 @@ static void prompt_onInit(ToxWindow *self, Tox *m)
}
ChatContext *ctx = self->chatwin;
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT + 1, x2, 0, 0);
ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - CHATBOX_HEIGHT, 0);
ctx->history = subwin(self->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2, 0, 0);
self->window_bar = subwin(self->window, WINDOW_BAR_HEIGHT, x2, y2 - (CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT), 0);
ctx->linewin = subwin(self->window, CHATBOX_HEIGHT, x2, y2 - WINDOW_BAR_HEIGHT, 0);
ctx->log = calloc(1, sizeof(struct chatlog));
ctx->hst = calloc(1, sizeof(struct history));
@ -577,14 +654,7 @@ static void prompt_onInit(ToxWindow *self, Tox *m)
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_PROMPT) == -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Warning: Log failed to initialize.");
}
}
prompt_init_log(self, m, self->name);
scrollok(ctx->history, 0);
wmove(self->window, y2 - CURS_Y_OFFSET, 0);
@ -611,7 +681,7 @@ ToxWindow *new_prompt(void)
ret->onConnectionChange = &prompt_onConnectionChange;
ret->onFriendRequest = &prompt_onFriendRequest;
strcpy(ret->name, "home");
strcpy(ret->name, "Home");
ChatContext *chatwin = calloc(1, sizeof(ChatContext));
StatusBar *stb = calloc(1, sizeof(StatusBar));

View File

@ -40,6 +40,9 @@ typedef struct FriendRequests {
struct friend_request request[MAX_FRIEND_REQUESTS];
} FriendRequests;
extern ToxWindow *prompt;
extern FriendRequests FrndRequests;
ToxWindow *new_prompt(void);
void prep_prompt_win(void);

View File

@ -114,25 +114,23 @@ static PyObject *python_api_get_status_message(PyObject *self, PyObject *args)
static PyObject *python_api_get_all_friends(PyObject *self, PyObject *args)
{
size_t i, ii;
FriendsList friends;
PyObject *cur, *ret;
char pubkey_buf[TOX_PUBLIC_KEY_SIZE * 2 + 1];
char pubkey_buf[TOX_PUBLIC_KEY_SIZE * 2 + 1];
if (!PyArg_ParseTuple(args, "")) {
return NULL;
}
friends = api_get_friendslist();
ret = PyList_New(0);
PyObject *ret = PyList_New(0);
for (i = 0; i < friends.num_friends; i++) {
for (ii = 0; ii < TOX_PUBLIC_KEY_SIZE; ii++) {
for (size_t i = 0; i < friends.num_friends; i++) {
for (size_t ii = 0; ii < TOX_PUBLIC_KEY_SIZE; ii++) {
snprintf(pubkey_buf + ii * 2, 3, "%02X", friends.list[i].pub_key[ii] & 0xff);
}
pubkey_buf[TOX_PUBLIC_KEY_SIZE * 2] = '\0';
cur = Py_BuildValue("(s,s)", friends.list[i].name, pubkey_buf);
PyObject *cur = Py_BuildValue("(s,s)", friends.list[i].name, pubkey_buf);
PyList_Append(ret, cur);
}
@ -264,14 +262,14 @@ PyMODINIT_FUNC PyInit_toxic_api(void)
void terminate_python(void)
{
struct python_registered_func *cur, *old;
if (python_commands.name != NULL) {
free(python_commands.name);
}
struct python_registered_func *cur = NULL;
for (cur = python_commands.next; cur != NULL;) {
old = cur;
struct python_registered_func *old = cur;
cur = cur->next;
free(old->name);
free(old);

View File

@ -73,6 +73,11 @@ static struct ui_strings {
const char *mplex_away;
const char *mplex_away_note;
const char *color_bar_bg;
const char *color_bar_fg;
const char *color_bar_accent;
const char *color_bar_notify;
} ui_strings = {
"ui",
"timestamps",
@ -100,6 +105,10 @@ static struct ui_strings {
"line_normal",
"mplex_away",
"mplex_away_note",
"color_bar_bg",
"color_bar_fg",
"color_bar_accent",
"color_bar_notify",
};
static void ui_defaults(struct user_settings *settings)
@ -116,7 +125,7 @@ static void ui_defaults(struct user_settings *settings)
settings->bell_on_invite = 0;
settings->colour_theme = DFLT_COLS;
settings->history_size = 700;
settings->notification_timeout = 3000;
settings->notification_timeout = 6000;
settings->show_typing_self = SHOW_TYPING_ON;
settings->show_typing_other = SHOW_TYPING_ON;
settings->show_welcome_msg = SHOW_WELCOME_MSG_ON;
@ -130,10 +139,7 @@ static void ui_defaults(struct user_settings *settings)
snprintf(settings->line_normal, LINE_HINT_MAX + 1, "%s", LINE_NORMAL);
settings->mplex_away = MPLEX_ON;
snprintf(settings->mplex_away_note,
sizeof(settings->mplex_away_note),
"%s",
MPLEX_AWAY_NOTE);
snprintf(settings->mplex_away_note, sizeof(settings->mplex_away_note), "%s", MPLEX_AWAY_NOTE);
}
static const struct keys_strings {
@ -205,11 +211,17 @@ static const struct audio_strings {
const char *input_device;
const char *output_device;
const char *VAD_threshold;
const char *conference_audio_channels;
const char *chat_audio_channels;
const char *push_to_talk;
} audio_strings = {
"audio",
"input_device",
"output_device",
"VAD_threshold",
"conference_audio_channels",
"chat_audio_channels",
"push_to_talk",
};
static void audio_defaults(struct user_settings *settings)
@ -217,6 +229,9 @@ static void audio_defaults(struct user_settings *settings)
settings->audio_in_dev = 0;
settings->audio_out_dev = 0;
settings->VAD_threshold = 5.0;
settings->conference_audio_channels = 1;
settings->chat_audio_channels = 2;
settings->push_to_talk = 0;
}
#endif
@ -330,7 +345,7 @@ int settings_load(struct user_settings *s, const char *patharg)
if (config_setting_lookup_int(setting, ui_strings.time_format, &time)) {
if (time == 12) {
snprintf(s->timestamp_format, sizeof(s->timestamp_format), "%s", "%I:%M:%S %p");
snprintf(s->timestamp_format, sizeof(s->timestamp_format), "%s", "%I:%M %p");
snprintf(s->log_timestamp_format, sizeof(s->log_timestamp_format), "%s", "%Y/%m/%d [%I:%M:%S %p]");
}
}
@ -339,6 +354,22 @@ int settings_load(struct user_settings *s, const char *patharg)
snprintf(s->timestamp_format, sizeof(s->timestamp_format), "%s", str);
}
if (config_setting_lookup_string(setting, ui_strings.color_bar_bg, &str)) {
snprintf(s->color_bar_bg, sizeof(s->color_bar_bg), "%s", str);
}
if (config_setting_lookup_string(setting, ui_strings.color_bar_fg, &str)) {
snprintf(s->color_bar_fg, sizeof(s->color_bar_fg), "%s", str);
}
if (config_setting_lookup_string(setting, ui_strings.color_bar_accent, &str)) {
snprintf(s->color_bar_accent, sizeof(s->color_bar_accent), "%s", str);
}
if (config_setting_lookup_string(setting, ui_strings.color_bar_notify, &str)) {
snprintf(s->color_bar_notify, sizeof(s->color_bar_notify), "%s", str);
}
if (config_setting_lookup_string(setting, ui_strings.log_timestamp_format, &str)) {
snprintf(s->log_timestamp_format, sizeof(s->log_timestamp_format), "%s", str);
}
@ -506,6 +537,15 @@ int settings_load(struct user_settings *s, const char *patharg)
s->audio_out_dev = s->audio_out_dev < 0 || s->audio_out_dev > MAX_DEVICES ? 0 : s->audio_out_dev;
config_setting_lookup_float(setting, audio_strings.VAD_threshold, &s->VAD_threshold);
config_setting_lookup_int(setting, audio_strings.conference_audio_channels, &s->conference_audio_channels);
s->conference_audio_channels = s->conference_audio_channels <= 0
|| s->conference_audio_channels > 2 ? 1 : s->conference_audio_channels;
config_setting_lookup_int(setting, audio_strings.chat_audio_channels, &s->chat_audio_channels);
s->chat_audio_channels = s->chat_audio_channels <= 0 || s->chat_audio_channels > 2 ? 2 : s->chat_audio_channels;
config_setting_lookup_bool(setting, audio_strings.push_to_talk, &s->push_to_talk);
}
#endif

View File

@ -32,7 +32,7 @@
#define PASSWORD_EVAL_MAX 512
/* holds user setting values */
/* Holds user setting values defined in the toxic config file. */
struct user_settings {
int autolog; /* boolean */
int alerts; /* boolean */
@ -68,6 +68,11 @@ struct user_settings {
char autorun_path[PATH_MAX];
char password_eval[PASSWORD_EVAL_MAX];
char color_bar_bg[COLOR_STR_SIZE];
char color_bar_fg[COLOR_STR_SIZE];
char color_bar_accent[COLOR_STR_SIZE];
char color_bar_notify[COLOR_STR_SIZE];
int key_next_tab;
int key_prev_tab;
int key_scroll_line_up;
@ -85,9 +90,14 @@ struct user_settings {
int audio_in_dev;
int audio_out_dev;
double VAD_threshold;
int conference_audio_channels;
int chat_audio_channels;
int push_to_talk; /* boolean */
#endif
};
extern struct user_settings *user_settings;
enum settings_values {
AUTOLOG_OFF = 0,
AUTOLOG_ON = 1,
@ -119,10 +129,10 @@ enum settings_values {
#define LINE_JOIN "-->"
#define LINE_QUIT "<--"
#define LINE_ALERT "-!-"
#define LINE_NORMAL "---"
#define TIMESTAMP_DEFAULT "%H:%M:%S"
#define LINE_NORMAL "-"
#define TIMESTAMP_DEFAULT "%H:%M"
#define LOG_TIMESTAMP_DEFAULT "%Y/%m/%d [%H:%M:%S]"
#define MPLEX_AWAY_NOTE "Detached from screen"
#define MPLEX_AWAY_NOTE "Away from keyboard, be back soon!"
int settings_load(struct user_settings *s, const char *patharg);

View File

@ -66,7 +66,7 @@
#include "windows.h"
#ifdef X11
#include "xtra.h"
#include "x11focus.h"
#endif
#ifdef AUDIO
@ -74,7 +74,7 @@
#ifdef VIDEO
#include "video_call.h"
#endif /* VIDEO */
ToxAV *av;
static ToxAV *av;
#endif /* AUDIO */
#ifdef PYTHON
@ -98,9 +98,15 @@ ToxWindow *prompt = NULL;
#define MAX_PASSWORD_LEN 64
struct Winthread Winthread;
struct cqueue_thread cqueue_thread;
struct av_thread av_thread;
static struct cqueue_thread cqueue_thread;
struct arg_opts arg_opts;
#ifdef AUDIO
static struct av_thread av_thread;
#endif
// This struct is not thread safe. It should only ever be written to from the main thread
// before any other thread that uses it is initialized.
struct user_settings *user_settings = NULL;
static struct user_password {
@ -205,10 +211,7 @@ void exit_toxic_success(Tox *m)
curl_global_cleanup();
#ifdef X11
/* We have to terminate xtra last coz reasons
* Please don't call this anywhere else coz trust me
*/
terminate_xtra();
terminate_x11focus();
#endif /* X11 */
exit(EXIT_SUCCESS);
@ -242,6 +245,12 @@ void cb_toxcore_logger(Tox *m, TOX_LOG_LEVEL level, const char *file, uint32_t l
}
}
/* Sets ncurses refresh rate. Lower values make it refresh more often. */
void set_window_refresh_rate(size_t refresh_rate)
{
timeout(refresh_rate);
}
static void init_term(void)
{
#if HAVE_WIDECHAR
@ -259,10 +268,14 @@ static void init_term(void)
keypad(stdscr, 1);
noecho();
nonl();
timeout(100);
set_window_refresh_rate(NCURSES_DEFAULT_REFRESH_RATE);
if (has_colors()) {
short bg_color = COLOR_BLACK;
short bar_bg_color = COLOR_BLUE;
short bar_fg_color = COLOR_WHITE;
short bar_accent_color = COLOR_CYAN;
short bar_notify_color = COLOR_YELLOW;
start_color();
if (user_settings->colour_theme == NATIVE_COLS) {
@ -271,15 +284,123 @@ static void init_term(void)
}
}
init_pair(0, COLOR_WHITE, COLOR_BLACK);
init_pair(1, COLOR_GREEN, bg_color);
init_pair(2, COLOR_CYAN, bg_color);
init_pair(3, COLOR_RED, bg_color);
init_pair(4, COLOR_BLUE, bg_color);
init_pair(5, COLOR_YELLOW, bg_color);
init_pair(6, COLOR_MAGENTA, bg_color);
init_pair(7, COLOR_BLACK, COLOR_BLACK);
init_pair(8, COLOR_BLACK, COLOR_WHITE);
if (!string_is_empty(user_settings->color_bar_bg)) {
if (strcmp(user_settings->color_bar_bg, "black") == 0) {
bar_bg_color = COLOR_BLACK;
} else if (strcmp(user_settings->color_bar_bg, "red") == 0) {
bar_bg_color = COLOR_RED;
} else if (strcmp(user_settings->color_bar_bg, "blue") == 0) {
bar_bg_color = COLOR_BLUE;
} else if (strcmp(user_settings->color_bar_bg, "cyan") == 0) {
bar_bg_color = COLOR_CYAN;
} else if (strcmp(user_settings->color_bar_bg, "green") == 0) {
bar_bg_color = COLOR_GREEN;
} else if (strcmp(user_settings->color_bar_bg, "yellow") == 0) {
bar_bg_color = COLOR_YELLOW;
} else if (strcmp(user_settings->color_bar_bg, "magenta") == 0) {
bar_bg_color = COLOR_MAGENTA;
} else if (strcmp(user_settings->color_bar_bg, "white") == 0) {
bar_bg_color = COLOR_WHITE;
} else {
bar_bg_color = COLOR_BLUE;
}
} else {
bar_bg_color = COLOR_BLUE;
}
if (!string_is_empty(user_settings->color_bar_fg)) {
if (strcmp(user_settings->color_bar_fg, "black") == 0) {
bar_fg_color = COLOR_BLACK;
} else if (strcmp(user_settings->color_bar_fg, "red") == 0) {
bar_fg_color = COLOR_RED;
} else if (strcmp(user_settings->color_bar_fg, "blue") == 0) {
bar_fg_color = COLOR_BLUE;
} else if (strcmp(user_settings->color_bar_fg, "cyan") == 0) {
bar_fg_color = COLOR_CYAN;
} else if (strcmp(user_settings->color_bar_fg, "green") == 0) {
bar_fg_color = COLOR_GREEN;
} else if (strcmp(user_settings->color_bar_fg, "yellow") == 0) {
bar_fg_color = COLOR_YELLOW;
} else if (strcmp(user_settings->color_bar_fg, "magenta") == 0) {
bar_fg_color = COLOR_MAGENTA;
} else if (strcmp(user_settings->color_bar_fg, "white") == 0) {
bar_fg_color = COLOR_WHITE;
} else {
bar_fg_color = COLOR_WHITE;
}
} else {
bar_fg_color = COLOR_WHITE;
}
if (!string_is_empty(user_settings->color_bar_accent)) {
if (strcmp(user_settings->color_bar_accent, "black") == 0) {
bar_accent_color = COLOR_BLACK;
} else if (strcmp(user_settings->color_bar_accent, "red") == 0) {
bar_accent_color = COLOR_RED;
} else if (strcmp(user_settings->color_bar_accent, "blue") == 0) {
bar_accent_color = COLOR_BLUE;
} else if (strcmp(user_settings->color_bar_accent, "cyan") == 0) {
bar_accent_color = COLOR_CYAN;
} else if (strcmp(user_settings->color_bar_accent, "green") == 0) {
bar_accent_color = COLOR_GREEN;
} else if (strcmp(user_settings->color_bar_accent, "yellow") == 0) {
bar_accent_color = COLOR_YELLOW;
} else if (strcmp(user_settings->color_bar_accent, "magenta") == 0) {
bar_accent_color = COLOR_MAGENTA;
} else if (strcmp(user_settings->color_bar_accent, "white") == 0) {
bar_accent_color = COLOR_WHITE;
} else {
bar_accent_color = COLOR_CYAN;
}
} else {
bar_accent_color = COLOR_CYAN;
}
if (!string_is_empty(user_settings->color_bar_notify)) {
if (strcmp(user_settings->color_bar_notify, "black") == 0) {
bar_notify_color = COLOR_BLACK;
} else if (strcmp(user_settings->color_bar_notify, "red") == 0) {
bar_notify_color = COLOR_RED;
} else if (strcmp(user_settings->color_bar_notify, "blue") == 0) {
bar_notify_color = COLOR_BLUE;
} else if (strcmp(user_settings->color_bar_notify, "cyan") == 0) {
bar_notify_color = COLOR_CYAN;
} else if (strcmp(user_settings->color_bar_notify, "green") == 0) {
bar_notify_color = COLOR_GREEN;
} else if (strcmp(user_settings->color_bar_notify, "yellow") == 0) {
bar_notify_color = COLOR_YELLOW;
} else if (strcmp(user_settings->color_bar_notify, "magenta") == 0) {
bar_notify_color = COLOR_MAGENTA;
} else if (strcmp(user_settings->color_bar_notify, "white") == 0) {
bar_notify_color = COLOR_WHITE;
} else {
bar_notify_color = COLOR_YELLOW;
}
} else {
bar_notify_color = COLOR_YELLOW;
}
init_pair(WHITE, COLOR_WHITE, COLOR_BLACK);
init_pair(GREEN, COLOR_GREEN, bg_color);
init_pair(CYAN, COLOR_CYAN, bg_color);
init_pair(RED, COLOR_RED, bg_color);
init_pair(BLUE, COLOR_BLUE, bg_color);
init_pair(YELLOW, COLOR_YELLOW, bg_color);
init_pair(MAGENTA, COLOR_MAGENTA, bg_color);
init_pair(BLACK, COLOR_BLACK, COLOR_BLACK);
init_pair(WHITE_BLUE, COLOR_WHITE, COLOR_BLUE);
init_pair(BLACK_WHITE, COLOR_BLACK, COLOR_WHITE);
init_pair(WHITE_BLACK, COLOR_WHITE, COLOR_BLACK);
init_pair(WHITE_GREEN, COLOR_WHITE, COLOR_GREEN);
init_pair(BLACK_BG, COLOR_BLACK, bar_bg_color);
init_pair(PURPLE_BG, COLOR_MAGENTA, bar_bg_color);
init_pair(BAR_TEXT, bar_fg_color, bar_bg_color);
init_pair(BAR_SOLID, bar_bg_color, bar_bg_color);
init_pair(BAR_ACCENT, bar_accent_color, bar_bg_color);
init_pair(BAR_NOTIFY, bar_notify_color, bar_bg_color);
init_pair(STATUS_ONLINE, COLOR_GREEN, bar_bg_color);
init_pair(STATUS_AWAY, COLOR_YELLOW, bar_bg_color);
init_pair(STATUS_BUSY, COLOR_RED, bar_bg_color);
}
refresh();
@ -326,9 +447,7 @@ static void cleanup_init_messages(void)
return;
}
int i;
for (i = 0; i < init_messages.num; ++i) {
for (int i = 0; i < init_messages.num; ++i) {
free(init_messages.msgs[i]);
}
@ -337,19 +456,16 @@ static void cleanup_init_messages(void)
static void print_init_messages(ToxWindow *toxwin)
{
int i;
for (i = 0; i < init_messages.num; ++i) {
for (int i = 0; i < init_messages.num; ++i) {
line_info_add(toxwin, NULL, NULL, NULL, SYS_MSG, 0, 0, init_messages.msgs[i]);
}
}
static void load_friendlist(Tox *m)
{
size_t i;
size_t numfriends = tox_self_get_friend_list_size(m);
for (i = 0; i < numfriends; ++i) {
for (size_t i = 0; i < numfriends; ++i) {
friendlist_onFriendAdded(NULL, m, i, false);
}
@ -698,6 +814,7 @@ static void init_tox_callbacks(Tox *m)
tox_callback_file_chunk_request(m, on_file_chunk_request);
tox_callback_file_recv_control(m, on_file_recv_control);
tox_callback_file_recv_chunk(m, on_file_recv_chunk);
tox_callback_friend_lossless_packet(m, on_lossless_custom_packet);
}
static void init_tox_options(struct Tox_Options *tox_opts)
@ -962,6 +1079,37 @@ static void do_toxic(Tox *m)
pthread_mutex_unlock(&Winthread.lock);
}
/* Set interface refresh flag. This should be called whenever the interface changes.
*
* This function is not thread safe.
*/
void flag_interface_refresh(void)
{
Winthread.flag_refresh = 1;
Winthread.last_refresh_flag = get_unix_time();
}
/* How long we wait to idle interface refreshing after last flag set. Should be no less than 2. */
#define ACTIVE_WIN_REFRESH_TIMEOUT 2
static void poll_interface_refresh_flag(void)
{
pthread_mutex_lock(&Winthread.lock);
bool flag = Winthread.flag_refresh;
time_t t = Winthread.last_refresh_flag;
pthread_mutex_unlock(&Winthread.lock);
if (flag == 1 && timed_out(t, ACTIVE_WIN_REFRESH_TIMEOUT)) {
pthread_mutex_lock(&Winthread.lock);
Winthread.flag_refresh = 0;
pthread_mutex_unlock(&Winthread.lock);
}
}
/* How often we refresh windows that aren't focused */
#define INACTIVE_WIN_REFRESH_RATE 10
void *thread_winref(void *data)
@ -969,11 +1117,12 @@ void *thread_winref(void *data)
Tox *m = (Tox *) data;
uint8_t draw_count = 0;
init_signal_catchers();
while (true) {
draw_active_window(m);
draw_count++;
draw_active_window(m);
if (Winthread.flag_resize) {
on_window_resize();
@ -987,6 +1136,8 @@ void *thread_winref(void *data)
pthread_mutex_lock(&Winthread.lock);
exit_toxic_success(m);
}
poll_interface_refresh_flag();
}
}
@ -1000,9 +1151,12 @@ void *thread_cqueue(void *data)
for (size_t i = 2; i < MAX_WINDOWS_NUM; ++i) {
ToxWindow *toxwin = get_window_ptr(i);
if ((toxwin != NULL) && (toxwin->type == WINDOW_TYPE_CHAT)
&& (get_friend_connection_status(toxwin->num) != TOX_CONNECTION_NONE)) {
cqueue_try_send(toxwin, m);
if ((toxwin != NULL) && (toxwin->type == WINDOW_TYPE_CHAT)) {
cqueue_check_unread(toxwin);
if (get_friend_connection_status(toxwin->num) != TOX_CONNECTION_NONE) {
cqueue_try_send(toxwin, m);
}
}
}
@ -1094,21 +1248,23 @@ static void parse_args(int argc, char *argv[])
};
const char *opts_str = "4bdehLotuxvc:f:l:n:r:p:P:T:";
int opt, indexptr;
long int port = 0;
int opt = 0;
int indexptr = 0;
while ((opt = getopt_long(argc, argv, opts_str, long_opts, &indexptr)) != -1) {
switch (opt) {
case '4':
case '4': {
arg_opts.use_ipv4 = 1;
break;
}
case 'b':
case 'b': {
arg_opts.debug = 1;
queue_init_message("stderr enabled");
break;
}
case 'c':
case 'c': {
if (optarg == NULL) {
queue_init_message("Invalid argument for option: %d", opt);
break;
@ -1121,17 +1277,20 @@ static void parse_args(int argc, char *argv[])
}
break;
}
case 'd':
case 'd': {
arg_opts.default_locale = 1;
queue_init_message("Using default POSIX locale");
break;
}
case 'e':
case 'e': {
arg_opts.encrypt_data = 1;
break;
}
case 'f':
case 'f': {
if (optarg == NULL) {
queue_init_message("Invalid argument for option: %d", opt);
break;
@ -1169,8 +1328,9 @@ static void parse_args(int argc, char *argv[])
queue_init_message("Using '%s' data file", DATA_FILE);
break;
}
case 'l':
case 'l': {
if (optarg) {
arg_opts.logging = true;
@ -1190,13 +1350,15 @@ static void parse_args(int argc, char *argv[])
}
break;
}
case 'L':
case 'L': {
arg_opts.disable_local_discovery = 1;
queue_init_message("Local discovery disabled");
break;
}
case 'n':
case 'n': {
if (optarg == NULL) {
queue_init_message("Invalid argument for option: %d", opt);
break;
@ -1204,48 +1366,38 @@ static void parse_args(int argc, char *argv[])
snprintf(arg_opts.nodes_path, sizeof(arg_opts.nodes_path), "%s", optarg);
break;
}
case 'o':
case 'o': {
arg_opts.no_connect = 1;
queue_init_message("DHT disabled");
break;
}
case 'p':
if (optarg == NULL) {
queue_init_message("Invalid argument for option: %d", opt);
break;
}
case 'p': {
arg_opts.proxy_type = TOX_PROXY_TYPE_SOCKS5;
snprintf(arg_opts.proxy_address, sizeof(arg_opts.proxy_address), "%s", optarg);
}
if (++optind > argc || argv[optind - 1][0] == '-') {
exit_toxic_err("Proxy error", FATALERR_PROXY);
}
// Intentional fallthrough
port = strtol(argv[optind - 1], NULL, 10);
if (port <= 0 || port > MAX_PORT_RANGE) {
exit_toxic_err("Proxy error", FATALERR_PROXY);
}
arg_opts.proxy_port = port;
break;
case 'P':
case 'P': {
if (optarg == NULL) {
queue_init_message("Invalid argument for option: %d", opt);
arg_opts.proxy_type = TOX_PROXY_TYPE_NONE;
break;
}
arg_opts.proxy_type = TOX_PROXY_TYPE_HTTP;
if (arg_opts.proxy_type == TOX_PROXY_TYPE_NONE) {
arg_opts.proxy_type = TOX_PROXY_TYPE_HTTP;
}
snprintf(arg_opts.proxy_address, sizeof(arg_opts.proxy_address), "%s", optarg);
if (++optind > argc || argv[optind - 1][0] == '-') {
exit_toxic_err("Proxy error", FATALERR_PROXY);
}
port = strtol(argv[optind - 1], NULL, 10);
long int port = strtol(argv[optind - 1], NULL, 10);
if (port <= 0 || port > MAX_PORT_RANGE) {
exit_toxic_err("Proxy error", FATALERR_PROXY);
@ -1253,8 +1405,9 @@ static void parse_args(int argc, char *argv[])
arg_opts.proxy_port = port;
break;
}
case 'r':
case 'r': {
if (optarg == NULL) {
queue_init_message("Invalid argument for option: %d", opt);
break;
@ -1267,18 +1420,20 @@ static void parse_args(int argc, char *argv[])
}
break;
}
case 't':
case 't': {
arg_opts.force_tcp = 1;
break;
}
case 'T':
case 'T': {
if (optarg == NULL) {
queue_init_message("Invalid argument for option: %d", opt);
break;
}
port = strtol(optarg, NULL, 10);
long int port = strtol(optarg, NULL, 10);
if (port <= 0 || port > MAX_PORT_RANGE) {
port = MAX_PORT_RANGE;
@ -1286,21 +1441,25 @@ static void parse_args(int argc, char *argv[])
arg_opts.tcp_port = port;
break;
}
case 'u':
case 'u': {
arg_opts.unencrypt_data = 1;
break;
}
case 'v':
case 'v': {
print_version();
exit(EXIT_SUCCESS);
}
case 'h':
// Intentional fallthrough
default:
default: {
print_usage();
exit(EXIT_SUCCESS);
}
}
}
}
@ -1350,26 +1509,13 @@ static void init_default_data_files(void)
free(user_config_dir);
}
// this doesn't do anything (yet)
#ifdef X11
void DnD_callback(const char *asdv, DropType dt)
{
UNUSED_VAR(asdv);
UNUSED_VAR(dt);
// if (dt != DT_plain)
// return;
// pthread_mutex_lock(&Winthread.lock);
// line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, asdv);
// pthread_mutex_unlock(&Winthread.lock);
}
#endif /* X11 */
int main(int argc, char **argv)
{
/* Make sure all written files are read/writeable only by the current user. */
umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
srand(time(NULL)); // We use rand() for trivial/non-security related things
parse_args(argc, argv);
/* Use the -b flag to enable stderr */
@ -1422,7 +1568,7 @@ int main(int argc, char **argv)
#ifdef X11
if (init_xtra(DnD_callback) == -1) {
if (init_x11focus() == -1) {
queue_init_message("X failed to initialize");
}
@ -1439,6 +1585,7 @@ int main(int argc, char **argv)
prompt = init_windows(m);
prompt_init_statusbar(prompt, m, !datafile_exists);
load_conferences(m);
set_active_window_index(0);
if (pthread_mutex_init(&Winthread.lock, NULL) != 0) {
exit_toxic_err("failed in main", FATALERR_MUTEX_INIT);
@ -1501,6 +1648,7 @@ int main(int argc, char **argv)
pthread_mutex_lock(&Winthread.lock);
print_init_messages(prompt);
flag_interface_refresh();
pthread_mutex_unlock(&Winthread.lock);
cleanup_init_messages();
@ -1521,7 +1669,7 @@ int main(int argc, char **argv)
pthread_mutex_lock(&Winthread.lock);
if (store_data(m, DATA_FILE) != 0) {
line_info_add(prompt, NULL, NULL, NULL, SYS_MSG, 0, RED, "WARNING: Failed to save to data file");
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, RED, "WARNING: Failed to save to data file");
}
pthread_mutex_unlock(&Winthread.lock);

View File

@ -48,6 +48,10 @@
#define TOXIC_MAX_NAME_LENGTH 32 /* Must be <= TOX_MAX_NAME_LENGTH */
#define KEY_IDENT_DIGITS 3 /* number of hex digits to display for the pub-key based identifier */
#define TIME_STR_SIZE 32
#define COLOR_STR_SIZE 10 /* should fit every color option */
#define NCURSES_DEFAULT_REFRESH_RATE 100
#define NCURSES_GAME_REFRESH_RATE 25
#ifndef MAX_PORT_RANGE
#define MAX_PORT_RANGE 65535
@ -75,7 +79,7 @@
#define T_KEY_C_DOWN 0x20D /* ctrl-down arrow */
#define T_KEY_TAB 0x09 /* TAB key */
#define ONLINE_CHAR "*"
#define ONLINE_CHAR "o"
#define OFFLINE_CHAR "o"
typedef enum _FATAL_ERRS {
@ -101,6 +105,11 @@ typedef enum _FATAL_ERRS {
void lock_status(void);
void unlock_status(void);
void flag_interface_refresh(void);
/* Sets ncurses refresh rate. Lower values make it refresh more often. */
void set_window_refresh_rate(size_t refresh_rate);
void exit_toxic_success(Tox *m);
void exit_toxic_err(const char *errmsg, int errcode);
@ -134,5 +143,9 @@ void on_file_recv(Tox *m, uint32_t friendnumber, uint32_t filenumber, uint32_t k
const uint8_t *filename, size_t filename_length, void *userdata);
void on_friend_typing(Tox *m, uint32_t friendnumber, bool is_typing, void *userdata);
void on_friend_read_receipt(Tox *m, uint32_t friendnumber, uint32_t receipt, void *userdata);
void on_lossless_custom_packet(Tox *m, uint32_t friendnumber, const uint8_t *data, size_t length, void *userdata);
extern char *DATA_FILE;
extern char *BLOCK_FILE;
#endif /* TOXIC_H */

View File

@ -52,7 +52,7 @@ void on_video_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t video_bit_rat
static void print_err(ToxWindow *self, const char *error_str)
{
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%s", error_str);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", error_str);
}
ToxAV *init_video(ToxWindow *self, Tox *tox)
@ -66,13 +66,13 @@ ToxAV *init_video(ToxWindow *self, Tox *tox)
CallControl.video_frame_duration = 10;
if (!CallControl.av) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Video failed to init with ToxAV instance");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Video failed to init with ToxAV instance");
return NULL;
}
if (init_video_devices(CallControl.av) == vde_InternalError) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to init video devices");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to init video devices");
return NULL;
}
@ -110,17 +110,17 @@ void read_video_device_callback(int16_t width, int16_t height, const uint8_t *y,
/* Drop frame if video sending is disabled */
if (this_call->video_bit_rate == 0 || this_call->status != cs_Active || this_call->vin_idx == -1) {
line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Video frame dropped.");
line_info_add(CallControl.prompt, false, NULL, NULL, SYS_MSG, 0, 0, "Video frame dropped.");
return;
}
if (toxav_video_send_frame(CallControl.av, friend_number, width, height, y, u, v, &error) == false) {
line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to send video frame");
line_info_add(CallControl.prompt, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to send video frame");
if (error == TOXAV_ERR_SEND_FRAME_NULL) {
line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to capture video frame");
line_info_add(CallControl.prompt, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to capture video frame");
} else if (error == TOXAV_ERR_SEND_FRAME_INVALID) {
line_info_add(CallControl.prompt, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to prepare video frame");
line_info_add(CallControl.prompt, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to prepare video frame");
}
}
}
@ -138,22 +138,22 @@ void write_video_device_callback(uint32_t friend_number, uint16_t width, uint16_
int start_video_transmission(ToxWindow *self, ToxAV *av, Call *call)
{
if (!self || !av) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to prepare video transmission");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to prepare video transmission");
return -1;
}
if (open_primary_video_device(vdt_input, &call->vin_idx, &call->video_width, &call->video_height) != vde_None) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to open input video device!");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to open input video device!");
return -1;
}
if (register_video_device_callback(self->num, call->vin_idx, read_video_device_callback, &self->num) != vde_None) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to register input video handler!");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to register input video handler!");
return -1;
}
if (!toxav_video_set_bit_rate(CallControl.av, self->num, call->video_bit_rate, NULL)) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to set video bit rate");
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to set video bit rate");
return -1;
}
@ -347,11 +347,11 @@ void cmd_res(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX
if (argc == 0) {
if (call->status == cs_Active && call->vin_idx != -1) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0,
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"Resolution of current call: %u x %u",
call->video_width, call->video_height);
} else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0,
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"Initial resolution for video calls: %u x %u",
CallControl.default_video_width, CallControl.default_video_height);
}
@ -411,7 +411,7 @@ void cmd_list_video_devices(WINDOW *window, ToxWindow *self, Tox *m, int argc, c
}
else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
return;
}
@ -447,7 +447,7 @@ void cmd_change_video_device(WINDOW *window, ToxWindow *self, Tox *m, int argc,
}
else {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Invalid type: %s", argv[1]);
return;
}

View File

@ -92,21 +92,21 @@ typedef struct VideoDevice {
} VideoDevice;
const char *dvideo_device_names[2]; /* Default device */
const char *video_devices_names[2][MAX_DEVICES]; /* Container of available devices */
static int size[2]; /* Size of above containers */
VideoDevice *video_devices_running[2][MAX_DEVICES] = {{NULL}}; /* Running devices */
uint32_t primary_video_device[2]; /* Primary device */
static const char *dvideo_device_names[2]; /* Default device */
static char *video_devices_names[2][MAX_DEVICES]; /* Container of available devices */
static int size[2]; /* Size of above containers */
static VideoDevice *video_devices_running[2][MAX_DEVICES] = {{NULL}}; /* Running devices */
static uint32_t primary_video_device[2]; /* Primary device */
static ToxAV *av = NULL;
/* q_mutex */
#define lock pthread_mutex_lock(&video_mutex)
#define unlock pthread_mutex_unlock(&video_mutex)
pthread_mutex_t video_mutex;
static pthread_mutex_t video_mutex;
bool video_thread_running = true,
video_thread_paused = true; /* Thread control */
static bool video_thread_running = true;
static bool video_thread_paused = true; /* Thread control */
void *video_thread_poll(void *);
@ -187,7 +187,7 @@ VideoDeviceError init_video_devices(void)
#if defined(__OSX__) || defined(__APPLE__)
if (osx_video_init((char **)video_devices_names[vdt_input], &size[vdt_input]) != 0) {
if (osx_video_init(&video_devices_names[vdt_input][0], &size[vdt_input]) != 0) {
return vde_InternalError;
}
@ -209,12 +209,24 @@ VideoDeviceError init_video_devices(void)
/* Query V4L for capture capabilities */
if (-1 != ioctl(fd, VIDIOC_QUERYCAP, &cap)) {
video_input_name = (char *)malloc(strlen((const char *)cap.card) + strlen(device_address) + 4);
if (video_input_name == NULL) {
close(fd);
return vde_InternalError;
}
strcpy(video_input_name, (char *)cap.card);
strcat(video_input_name, " (");
strcat(video_input_name, (char *)device_address);
strcat(video_input_name, ")");
} else {
video_input_name = (char *)malloc(strlen(device_address) + 3);
if (video_input_name == NULL) {
close(fd);
return vde_InternalError;
}
strcpy(video_input_name, "(");
strcat(video_input_name, device_address);
strcat(video_input_name, ")");
@ -229,6 +241,9 @@ VideoDeviceError init_video_devices(void)
#endif
size[vdt_output] = 1;
// TODO(iphydf): String literals are const char *. This may need to be
// copied, or if we're not owning any output device names, it should be
// const and video_devices_names needs to be split.
char *video_output_name = "Toxic Video Receiver";
video_devices_names[vdt_output][0] = video_output_name;
@ -262,7 +277,7 @@ VideoDeviceError terminate_video_devices(void)
int i;
for (i = 0; i < size[vdt_input]; ++i) {
free((void *)video_devices_names[vdt_input][i]);
free(video_devices_names[vdt_input][i]);
}
if (pthread_mutex_destroy(&video_mutex) != 0) {
@ -852,7 +867,7 @@ void print_video_devices(ToxWindow *self, VideoDeviceType type)
int i;
for (i = 0; i < size[type]; ++i) {
line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%d: %s", i, video_devices_names[type][i]);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%d: %s", i, video_devices_names[type][i]);
}
return;

View File

@ -37,16 +37,14 @@
#include "toxic.h"
#include "windows.h"
extern char *DATA_FILE;
extern struct Winthread Winthread;
#ifdef GAMES
#include "game_base.h"
#endif
ToxWindow *windows[MAX_WINDOWS_NUM];
static uint8_t active_window_index;
static int num_active_windows;
extern ToxWindow *prompt;
extern struct user_settings *user_settings;
/* CALLBACKS START */
void on_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
{
@ -73,6 +71,8 @@ void on_friend_connection_status(Tox *m, uint32_t friendnumber, Tox_Connection c
windows[i]->onConnectionChange(windows[i], m, friendnumber, connection_status);
}
}
flag_interface_refresh();
}
void on_friend_typing(Tox *m, uint32_t friendnumber, bool is_typing, void *userdata)
@ -88,6 +88,8 @@ void on_friend_typing(Tox *m, uint32_t friendnumber, bool is_typing, void *userd
windows[i]->onTypingChange(windows[i], m, friendnumber, is_typing);
}
}
flag_interface_refresh();
}
void on_friend_message(Tox *m, uint32_t friendnumber, Tox_Message_Type type, const uint8_t *string, size_t length,
@ -119,6 +121,8 @@ void on_friend_name(Tox *m, uint32_t friendnumber, const uint8_t *string, size_t
}
}
flag_interface_refresh();
store_data(m, DATA_FILE);
}
@ -136,6 +140,8 @@ void on_friend_status_message(Tox *m, uint32_t friendnumber, const uint8_t *stri
windows[i]->onStatusMessageChange(windows[i], friendnumber, msg, length);
}
}
flag_interface_refresh();
}
void on_friend_status(Tox *m, uint32_t friendnumber, Tox_User_Status status, void *userdata)
@ -147,6 +153,8 @@ void on_friend_status(Tox *m, uint32_t friendnumber, Tox_User_Status status, voi
windows[i]->onStatusChange(windows[i], m, friendnumber, status);
}
}
flag_interface_refresh();
}
void on_friend_added(Tox *m, uint32_t friendnumber, bool sort)
@ -182,7 +190,7 @@ void on_conference_invite(Tox *m, uint32_t friendnumber, Tox_Conference_Type typ
for (uint8_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
if (windows[i] != NULL && windows[i]->onConferenceInvite != NULL) {
windows[i]->onConferenceInvite(windows[i], m, friendnumber, type, (char *) conference_pub_key, length);
windows[i]->onConferenceInvite(windows[i], m, friendnumber, type, (const char *) conference_pub_key, length);
}
}
}
@ -196,6 +204,8 @@ void on_conference_peer_list_changed(Tox *m, uint32_t conferencenumber, void *us
windows[i]->onConferenceNameListChange(windows[i], m, conferencenumber);
}
}
flag_interface_refresh();
}
void on_conference_peer_name(Tox *m, uint32_t conferencenumber, uint32_t peernumber, const uint8_t *name,
@ -234,7 +244,7 @@ void on_file_chunk_request(Tox *m, uint32_t friendnumber, uint32_t filenumber, u
{
UNUSED_VAR(userdata);
struct FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber);
FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber);
if (!ft) {
return;
@ -257,7 +267,7 @@ void on_file_recv_chunk(Tox *m, uint32_t friendnumber, uint32_t filenumber, uint
{
UNUSED_VAR(userdata);
struct FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber);
FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber);
if (!ft) {
return;
@ -265,7 +275,7 @@ void on_file_recv_chunk(Tox *m, uint32_t friendnumber, uint32_t filenumber, uint
for (uint8_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
if (windows[i] != NULL && windows[i]->onFileRecvChunk != NULL) {
windows[i]->onFileRecvChunk(windows[i], m, friendnumber, filenumber, position, (char *) data, length);
windows[i]->onFileRecvChunk(windows[i], m, friendnumber, filenumber, position, (const char *) data, length);
}
}
}
@ -275,7 +285,7 @@ void on_file_recv_control(Tox *m, uint32_t friendnumber, uint32_t filenumber, To
{
UNUSED_VAR(userdata);
struct FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber);
FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber);
if (!ft) {
return;
@ -306,7 +316,7 @@ void on_file_recv(Tox *m, uint32_t friendnumber, uint32_t filenumber, uint32_t k
for (uint8_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
if (windows[i] != NULL && windows[i]->onFileRecv != NULL) {
windows[i]->onFileRecv(windows[i], m, friendnumber, filenumber, file_size, (char *) filename,
windows[i]->onFileRecv(windows[i], m, friendnumber, filenumber, file_size, (const char *) filename,
filename_length);
}
}
@ -322,6 +332,53 @@ void on_friend_read_receipt(Tox *m, uint32_t friendnumber, uint32_t receipt, voi
}
}
}
void on_lossless_custom_packet(Tox *m, uint32_t friendnumber, const uint8_t *data, size_t length, void *userdata)
{
UNUSED_VAR(userdata);
if (length == 0 || data == NULL) {
return;
}
uint8_t type = data[0];
switch (type) {
#ifdef GAMES
case CUSTOM_PACKET_GAME_INVITE: {
for (size_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
ToxWindow *window = windows[i];
if (window != NULL && window->onGameInvite != NULL) {
window->onGameInvite(window, m, friendnumber, data + 1, length - 1);
}
}
break;
}
case CUSTOM_PACKET_GAME_DATA: {
for (size_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
ToxWindow *window = windows[i];
if (window != NULL && window->onGameData != NULL) {
window->onGameData(window, m, friendnumber, data + 1, length - 1);
}
}
break;
}
#endif // GAMES
default: {
fprintf(stderr, "Got unknown custom packet of type: %u\n", type);
return;
}
}
}
/* CALLBACKS END */
int add_window(Tox *m, ToxWindow *w)
@ -336,7 +393,7 @@ int add_window(Tox *m, ToxWindow *w)
}
w->index = i;
w->window = newwin(LINES - 2, COLS, 0, 0);
w->window = newwin(LINES, COLS, 0, 0);
if (w->window == NULL) {
return -1;
@ -367,14 +424,18 @@ void set_active_window_index(uint8_t index)
}
}
/* Shows next window when tab or back-tab is pressed */
void set_next_window(int ch)
/* Displays the next window if `ch` is equal to the next window key binding.
* Otherwise displays the previous window.
*/
static void set_next_window(int ch)
{
uint8_t index = 0;
if (ch == user_settings->key_next_tab) {
for (uint8_t i = active_window_index + 1; i < MAX_WINDOWS_NUM; ++i) {
if (windows[i] != NULL) {
set_active_window_index(i);
return;
index = i;
break;
}
}
} else {
@ -382,33 +443,46 @@ void set_next_window(int ch)
for (uint8_t i = start; i > 0; --i) {
if (windows[i] != NULL) {
set_active_window_index(i);
return;
index = i;
break;
}
}
}
set_active_window_index(0);
set_active_window_index(index);
}
/* Deletes window w and cleans up */
void del_window(ToxWindow *w)
{
set_active_window_index(0);
uint8_t idx = w->index;
delwin(w->window_bar);
delwin(w->window);
free(windows[idx]);
free(w);
windows[idx] = NULL;
clear();
refresh();
--num_active_windows;
if (num_active_windows > 0) {
if (active_window_index == 2) { // if closing current window would bring us to friend list
set_next_window(-1); // skip back to the home window instead. FIXME: magic numbers
}
set_next_window(-1);
--num_active_windows;
}
}
ToxWindow *init_windows(Tox *m)
{
if (COLS <= CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT) {
exit_toxic_err("add_window() for prompt failed in init_windows", FATALERR_WININIT);
}
prompt = new_prompt();
int n_prompt = add_window(m, prompt);
if (n_prompt < 0) {
@ -430,28 +504,47 @@ void on_window_resize(void)
refresh();
clear();
/* equivalent to LINES and COLS */
int x2, y2;
getmaxyx(stdscr, y2, x2);
y2 -= 2;
if (y2 <= 0 || x2 <= 0) {
return;
}
int x2;
int y2;
for (uint8_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
if (windows[i] == NULL) {
continue;
}
ToxWindow *w = windows[i];
if (windows[i]->type == WINDOW_TYPE_FRIEND_LIST) {
delwin(w->window);
w->window = newwin(y2, x2, 0, 0);
if (w == NULL) {
continue;
}
if (w->type == WINDOW_TYPE_FRIEND_LIST) {
delwin(w->window_bar);
delwin(w->window);
w->window = newwin(LINES, COLS, 0, 0);
w->window_bar = subwin(w->window, WINDOW_BAR_HEIGHT, COLS, LINES - 2, 0);
continue;
}
#ifdef GAMES
if (w->type == WINDOW_TYPE_GAME) {
delwin(w->window_bar);
delwin(w->window);
delwin(w->game->window);
w->window = newwin(LINES, COLS, 0, 0);
getmaxyx(w->window, y2, x2);
if (y2 <= 0 || x2 <= 0) {
fprintf(stderr, "Failed to resize game window: max_x: %d, max_y: %d\n", x2, y2);
delwin(w->window);
continue;
}
w->window_bar = subwin(w->window, WINDOW_BAR_HEIGHT, COLS, LINES - 2, 0);
w->game->window = subwin(w->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2, 0, 0);
continue;
}
#endif // GAMES
if (w->help->active) {
wclear(w->help->win);
}
@ -465,22 +558,33 @@ void on_window_resize(void)
delwin(w->chatwin->linewin);
delwin(w->chatwin->history);
delwin(w->window_bar);
delwin(w->window);
w->window = newwin(y2, x2, 0, 0);
w->chatwin->linewin = subwin(w->window, CHATBOX_HEIGHT, x2, y2 - CHATBOX_HEIGHT, 0);
w->window = newwin(LINES, COLS, 0, 0);
getmaxyx(w->window, y2, x2);
if (y2 <= 0 || x2 <= 0) {
fprintf(stderr, "Failed to resize window: max_x: %d, max_y: %d\n", x2, y2);
delwin(w->window);
continue;
}
if (w->show_peerlist) {
w->chatwin->history = subwin(w->window, y2 - CHATBOX_HEIGHT + 1, x2 - SIDEBAR_WIDTH - 1, 0, 0);
w->chatwin->sidebar = subwin(w->window, y2 - CHATBOX_HEIGHT + 1, SIDEBAR_WIDTH, 0, x2 - SIDEBAR_WIDTH);
w->chatwin->history = subwin(w->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2 - SIDEBAR_WIDTH - 1, 0, 0);
w->chatwin->sidebar = subwin(w->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, SIDEBAR_WIDTH, 0, x2 - SIDEBAR_WIDTH);
} else {
w->chatwin->history = subwin(w->window, y2 - CHATBOX_HEIGHT + 1, x2, 0, 0);
w->chatwin->history = subwin(w->window, y2 - CHATBOX_HEIGHT - WINDOW_BAR_HEIGHT, x2, 0, 0);
if (w->type != WINDOW_TYPE_CONFERENCE) {
w->stb->topline = subwin(w->window, 2, x2, 0, 0);
w->stb->topline = subwin(w->window, TOP_BAR_HEIGHT, x2, 0, 0);
}
}
w->window_bar = subwin(w->window, WINDOW_BAR_HEIGHT, x2, y2 - (CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT), 0);
w->chatwin->linewin = subwin(w->window, CHATBOX_HEIGHT, x2, y2 - WINDOW_BAR_HEIGHT, 0);
#ifdef AUDIO
if (w->chatwin->infobox.active) {
@ -491,82 +595,103 @@ void on_window_resize(void)
#endif /* AUDIO */
scrollok(w->chatwin->history, 0);
wmove(w->window, y2 - CURS_Y_OFFSET, 0);
}
}
static void draw_window_tab(ToxWindow *toxwin)
static void draw_window_tab(WINDOW *win, ToxWindow *toxwin, bool active_window)
{
pthread_mutex_lock(&Winthread.lock);
if (toxwin->alert != WINDOW_ALERT_NONE) {
attron(COLOR_PAIR(toxwin->alert));
}
bool has_alert = toxwin->alert != WINDOW_ALERT_NONE;
unsigned int pending_messages = toxwin->pending_messages;
pthread_mutex_unlock(&Winthread.lock);
clrtoeol();
printw(" [%s]", toxwin->name);
WINDOW_TYPE type = toxwin->type;
pthread_mutex_lock(&Winthread.lock);
if (toxwin->alert != WINDOW_ALERT_NONE) {
attroff(COLOR_PAIR(toxwin->alert));
if (active_window) {
wattron(win, A_BOLD | COLOR_PAIR(BAR_ACCENT));
wprintw(win, " [");
wattroff(win, COLOR_PAIR(BAR_ACCENT));
wattron(win, COLOR_PAIR(BAR_TEXT));
} else {
if (has_alert) {
wattron(win, COLOR_PAIR(BAR_ACCENT));
wprintw(win, " [");
wattroff(win, COLOR_PAIR(BAR_ACCENT));
wattron(win, A_BOLD | COLOR_PAIR(toxwin->alert));
} else {
wattron(win, COLOR_PAIR(BAR_ACCENT));
wprintw(win, " [");
wattroff(win, COLOR_PAIR(BAR_ACCENT));
wattron(win, COLOR_PAIR(BAR_TEXT));
}
}
pthread_mutex_unlock(&Winthread.lock);
if (active_window || (type == WINDOW_TYPE_PROMPT || type == WINDOW_TYPE_FRIEND_LIST)) {
wprintw(win, "%s", toxwin->name);
} else {
if (pending_messages > 0) {
wprintw(win, "%u", pending_messages);
} else {
wprintw(win, "-");
}
}
if (active_window) {
wattroff(win, COLOR_PAIR(BAR_TEXT));
wattron(win, COLOR_PAIR(BAR_ACCENT));
wprintw(win, "]");
wattroff(win, A_BOLD | COLOR_PAIR(BAR_ACCENT));
} else {
if (has_alert) {
wattroff(win, A_BOLD | COLOR_PAIR(toxwin->alert));
wattron(win, COLOR_PAIR(BAR_ACCENT));
wprintw(win, "]");
wattroff(win, COLOR_PAIR(BAR_ACCENT));
} else {
wattroff(win, COLOR_PAIR(BAR_TEXT));
wattron(win, COLOR_PAIR(BAR_ACCENT));
wprintw(win, "]");
wattroff(win, COLOR_PAIR(BAR_ACCENT));
}
}
}
static void draw_bar(void)
void draw_window_bar(ToxWindow *self)
{
int y, x;
WINDOW *win = self->window_bar;
wclear(win);
ToxWindow *w = windows[active_window_index];
if (w == NULL) {
return;
if (self->scroll_pause) {
wattron(win, A_BLINK | A_BOLD | COLOR_PAIR(BAR_NOTIFY));
wprintw(win, "^");
wattroff(win, A_BLINK | A_BOLD | COLOR_PAIR(BAR_NOTIFY));
} else {
wattron(win, COLOR_PAIR(BAR_TEXT));
wprintw(win, " ");
wattroff(win, COLOR_PAIR(BAR_TEXT));
}
// save current cursor position
getyx(w->window, y, x);
attron(COLOR_PAIR(BLUE));
mvhline(LINES - 2, 0, '_', COLS);
attroff(COLOR_PAIR(BLUE));
move(LINES - 1, 0);
for (uint8_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
if (windows[i] == NULL) {
continue;
}
if (i == active_window_index) {
#ifdef URXVT_FIX
attron(A_BOLD | COLOR_PAIR(GREEN));
} else {
#endif
attron(A_BOLD);
}
draw_window_tab(windows[i]);
if (i == active_window_index) {
#ifdef URXVT_FIX
attroff(A_BOLD | COLOR_PAIR(GREEN));
} else {
#endif
attroff(A_BOLD);
}
bool active_window = i == active_window_index;
draw_window_tab(win, windows[i], active_window);
}
// restore cursor position after drawing
move(y, x);
int cur_x;
int cur_y;
getyx(win, cur_y, cur_x);
refresh();
UNUSED_VAR(cur_y);
wattron(win, COLOR_PAIR(BAR_TEXT));
mvwhline(win, 0, cur_x, ' ', COLS - cur_x);
wattroff(win, COLOR_PAIR(BAR_TEXT));
}
/*
@ -666,13 +791,54 @@ void draw_active_window(Tox *m)
pthread_mutex_lock(&Winthread.lock);
a->alert = WINDOW_ALERT_NONE;
a->pending_messages = 0;
bool flag_refresh = Winthread.flag_refresh;
pthread_mutex_unlock(&Winthread.lock);
draw_bar();
if (flag_refresh) {
touchwin(a->window);
a->onDraw(a, m);
wrefresh(a->window);
}
touchwin(a->window);
a->onDraw(a, m);
wrefresh(a->window);
#ifdef AUDIO
else if (a->is_call && timed_out(a->chatwin->infobox.lastupdate, 1)) {
touchwin(a->window);
a->onDraw(a, m);
wrefresh(a->window);
}
#endif // AUDIO
#ifdef GAMES
if (a->type == WINDOW_TYPE_GAME) {
if (!flag_refresh) { // we always want to be continously refreshing game windows
touchwin(a->window);
a->onDraw(a, m);
wrefresh(a->window);
}
int ch = getch();
if (ch == ERR) {
return;
}
pthread_mutex_lock(&Winthread.lock);
flag_interface_refresh();
pthread_mutex_unlock(&Winthread.lock);
if (ch == user_settings->key_next_tab || ch == user_settings->key_prev_tab) {
set_next_window(ch);
}
a->onKey(a, m, ch, false);
return;
}
#endif // GAMES
wint_t ch = 0;
int printable = get_current_char(&ch);
@ -681,6 +847,10 @@ void draw_active_window(Tox *m)
return;
}
pthread_mutex_lock(&Winthread.lock);
flag_interface_refresh();
pthread_mutex_unlock(&Winthread.lock);
if (printable == 0 && (ch == user_settings->key_next_tab || ch == user_settings->key_prev_tab)) {
set_next_window((int) ch);
return;
@ -756,18 +926,59 @@ int get_num_active_windows(void)
return num_active_windows;
}
/* destroys all chat and conference windows (should only be called on shutdown) */
void kill_all_windows(Tox *m)
/* Returns the number of active windows of given type. */
size_t get_num_active_windows_type(WINDOW_TYPE type)
{
for (uint8_t i = 2; i < MAX_WINDOWS_NUM; ++i) {
if (windows[i] == NULL) {
size_t count = 0;
for (size_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
ToxWindow *w = windows[i];
if (w == NULL) {
continue;
}
if (windows[i]->type == WINDOW_TYPE_CHAT) {
kill_chat_window(windows[i], m);
} else if (windows[i]->type == WINDOW_TYPE_CONFERENCE) {
free_conference(windows[i], windows[i]->num);
if (w->type == type) {
++count;
}
}
return count;
}
/* destroys all chat and conference windows (should only be called on shutdown) */
void kill_all_windows(Tox *m)
{
for (size_t i = 2; i < MAX_WINDOWS_NUM; ++i) {
ToxWindow *w = windows[i];
if (w == NULL) {
continue;
}
switch (w->type) {
case WINDOW_TYPE_CHAT: {
kill_chat_window(w, m);
break;
}
case WINDOW_TYPE_CONFERENCE: {
free_conference(w, w->num);
break;
}
#ifdef GAMES
case WINDOW_TYPE_GAME: {
game_kill(w);
break;
}
#endif // GAMES
default: {
break;
}
}
}

View File

@ -36,12 +36,21 @@
#include "toxic.h"
#define MAX_WINDOWS_NUM 16
#define MAX_WINDOWS_NUM 20
#define MAX_WINDOW_NAME_LENGTH 22
#define CURS_Y_OFFSET 1 /* y-axis cursor offset for chat contexts */
#define CHATBOX_HEIGHT 2
#define CHATBOX_HEIGHT 1
#define TOP_BAR_HEIGHT 1
#define WINDOW_BAR_HEIGHT 1
/* Curses foreground colours (background is black) */
typedef enum CustomPacket {
CUSTOM_PACKET_GAME_INVITE = 160,
CUSTOM_PACKET_GAME_DATA = 161,
} CustomPacket;
/* ncurses colour pairs as FOREGROUND_BACKGROUND. No background defaults to black. */
typedef enum {
WHITE,
GREEN,
@ -51,14 +60,27 @@ typedef enum {
YELLOW,
MAGENTA,
BLACK,
BLACK_WHITE,
WHITE_BLACK,
WHITE_BLUE,
WHITE_GREEN,
BAR_TEXT,
STATUS_ONLINE,
BAR_ACCENT,
PURPLE_BG,
BLACK_BG,
STATUS_BUSY,
STATUS_AWAY,
BAR_NOTIFY,
BAR_SOLID,
} C_COLOURS;
/* tab alert types: lower types take priority (this relies on the order of C_COLOURS) */
typedef enum {
WINDOW_ALERT_NONE = 0,
WINDOW_ALERT_0 = GREEN,
WINDOW_ALERT_1 = RED,
WINDOW_ALERT_2 = MAGENTA,
WINDOW_ALERT_0 = STATUS_ONLINE,
WINDOW_ALERT_1 = BAR_ACCENT,
WINDOW_ALERT_2 = PURPLE_BG,
} WINDOW_ALERTS;
typedef enum {
@ -66,19 +88,34 @@ typedef enum {
WINDOW_TYPE_CHAT,
WINDOW_TYPE_CONFERENCE,
WINDOW_TYPE_FRIEND_LIST,
#ifdef GAMES
WINDOW_TYPE_GAME,
#endif
} WINDOW_TYPE;
/* Fixes text color problem on some terminals.
Uncomment if necessary */
/* #define URXVT_FIX */
/*
* Used to control access to global variables via a mutex, as well as to handle signals.
* Any file, variable or data structure that is used by the UI/Window thread and any other thread
* must be guarded by `lock`.
*
* There should only ever be one instance of this struct.
*/
struct Winthread {
pthread_t tid;
pthread_mutex_t lock;
volatile sig_atomic_t sig_exit_toxic;
volatile sig_atomic_t flag_resize;
volatile sig_atomic_t flag_refresh;
volatile sig_atomic_t last_refresh_flag;
};
extern struct Winthread Winthread;
struct cqueue_thread {
pthread_t tid;
};
@ -112,12 +149,18 @@ struct arg_opts {
uint16_t tcp_port;
};
extern struct arg_opts arg_opts;
typedef struct ToxWindow ToxWindow;
typedef struct StatusBar StatusBar;
typedef struct PromptBuf PromptBuf;
typedef struct ChatContext ChatContext;
typedef struct Help Help;
#ifdef GAMES
typedef struct GameData GameData;
#endif
struct ToxWindow {
/* ncurses */
bool(*onKey)(ToxWindow *, Tox *, wint_t, bool);
@ -144,6 +187,11 @@ struct ToxWindow {
void(*onTypingChange)(ToxWindow *, Tox *, uint32_t, bool);
void(*onReadReceipt)(ToxWindow *, Tox *, uint32_t, uint32_t);
#ifdef GAMES
void(*onGameInvite)(ToxWindow *, Tox *, uint32_t, const uint8_t *, size_t);
void(*onGameData)(ToxWindow *, Tox *, uint32_t, const uint8_t *, size_t);
#endif // GAMES
#ifdef AUDIO
void(*onInvite)(ToxWindow *, ToxAV *, uint32_t, int);
@ -167,6 +215,8 @@ struct ToxWindow {
char name[TOXIC_MAX_NAME_LENGTH + 1];
uint32_t num; /* corresponds to friendnumber in chat windows */
uint8_t index; /* This window's index in the windows array */
bool scroll_pause; /* true if this window is not scrolled to the bottom */
unsigned int pending_messages; /* # of new messages in this window since the last time it was focused */
int x;
WINDOW_TYPE type;
@ -179,9 +229,16 @@ struct ToxWindow {
StatusBar *stb;
Help *help;
#ifdef GAMES
GameData *game;
#endif
WINDOW *window;
WINDOW *window_bar;
};
extern ToxWindow *windows[MAX_WINDOWS_NUM];
/* statusbar info holder */
struct StatusBar {
WINDOW *topline;
@ -263,9 +320,14 @@ void on_window_resize(void);
void force_refresh(WINDOW *w);
ToxWindow *get_window_ptr(size_t i);
ToxWindow *get_active_window(void);
void draw_window_bar(ToxWindow *self);
/* Returns the number of active windows of given type. */
size_t get_num_active_windows_type(WINDOW_TYPE type);
/* refresh inactive windows to prevent scrolling bugs.
call at least once per second */
void refresh_inactive_windows(void);
#endif /* WINDOWS_H */
#endif // WINDOWS_H

87
src/x11focus.c Normal file
View File

@ -0,0 +1,87 @@
/* x11focus.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 "x11focus.h"
#ifndef __APPLE__
#include <X11/Xlib.h>
static struct Focus {
Display *display;
Window terminal_window;
} Focus;
static long unsigned int focused_window_id(void)
{
if (!Focus.display) {
return 0;
}
Window focus;
int revert;
XLockDisplay(Focus.display);
XGetInputFocus(Focus.display, &focus, &revert);
XUnlockDisplay(Focus.display);
return focus;
}
bool is_focused(void)
{
if (!Focus.display) {
return false;
}
return Focus.terminal_window == focused_window_id();
}
int init_x11focus(void)
{
if (XInitThreads() == 0) {
return -1;
}
Focus.display = XOpenDisplay(NULL);
if (!Focus.display) {
return -1;
}
Focus.terminal_window = focused_window_id();
return 0;
}
void terminate_x11focus(void)
{
if (!Focus.display || !Focus.terminal_window) {
return;
}
XLockDisplay(Focus.display);
XCloseDisplay(Focus.display);
XUnlockDisplay(Focus.display);
}
#endif /* !__APPLE__ */

View File

@ -1,7 +1,7 @@
/* xtra.h
*
*
* Copyright (C) 2014 Toxic All Rights Reserved.
* Copyright (C) 2020 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
@ -20,21 +20,15 @@
*
*/
#ifndef XTRA_H
#define XTRA_H
#ifndef X11FOCUS_H
#define X11FOCUS_H
#include <stdbool.h>
/* NOTE: If no xlib present don't compile */
typedef enum {
DT_plain,
DT_file_list
}
DropType;
int init_x11focus(void);
void terminate_x11focus(void);
bool is_focused(void);
typedef void (*drop_callback)(const char *, DropType);
int init_xtra(drop_callback d);
void terminate_xtra(void);
int is_focused(void); /* returns bool */
#endif /* XTRA_H */
#endif /* X11FOCUS */

View File

@ -1,425 +0,0 @@
/* xtra.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 "misc_tools.h"
#include "xtra.h"
#ifndef __APPLE__
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
const Atom XtraTerminate = 1;
const Atom XtraNil = 0;
static Atom XdndAware;
static Atom XdndEnter;
static Atom XdndLeave;
static Atom XdndPosition;
static Atom XdndStatus;
static Atom XdndDrop;
static Atom XdndSelection;
static Atom XdndDATA;
static Atom XdndTypeList;
static Atom XdndActionCopy;
static Atom XdndFinished;
struct Xtra {
drop_callback on_drop;
Display *display;
Window terminal_window;
Window proxy_window;
Window source_window; /* When we have a drop */
Atom handling_version;
Atom expecting_type;
} Xtra;
typedef struct Property {
unsigned char *data;
int read_format;
unsigned long read_num;
Atom read_type;
} Property;
static Property read_property(Window s, Atom p)
{
Atom read_type;
int read_format;
unsigned long read_num;
unsigned long left_bytes;
unsigned char *data = NULL;
int read_bytes = 1024;
/* Keep trying to read the property until there are no bytes unread */
do {
if (data) {
XFree(data);
}
XGetWindowProperty(Xtra.display, s,
p, 0,
read_bytes,
False, AnyPropertyType,
&read_type, &read_format,
&read_num, &left_bytes,
&data);
read_bytes *= 2;
} while (left_bytes != 0);
Property property = {data, read_format, read_num, read_type};
return property;
}
static Atom get_dnd_type(long *a, int l)
{
int i = 0;
for (; i < l; i ++) {
if (a[i] != XtraNil) {
return a[i]; /* Get first valid */
}
}
return XtraNil;
}
/* TODO maybe support only certain types in the future */
static void handle_xdnd_enter(XClientMessageEvent *e)
{
Xtra.handling_version = (e->data.l[1] >> 24);
if ((e->data.l[1] & 1)) {
// Fetch the list of possible conversions
Property p = read_property(e->data.l[0], XdndTypeList);
Xtra.expecting_type = get_dnd_type((long *)p.data, p.read_num);
XFree(p.data);
} else {
// Use the available list
Xtra.expecting_type = get_dnd_type(e->data.l + 2, 3);
}
}
static void handle_xdnd_position(XClientMessageEvent *e)
{
XEvent ev = {
.xclient = {
.type = ClientMessage,
.display = e->display,
.window = e->data.l[0],
.message_type = XdndStatus,
.format = 32,
.data = {
.l = {
Xtra.proxy_window,
(Xtra.expecting_type != XtraNil),
0, 0,
XdndActionCopy
}
}
}
};
XSendEvent(Xtra.display, e->data.l[0], False, NoEventMask, &ev);
XFlush(Xtra.display);
}
static void handle_xdnd_drop(XClientMessageEvent *e)
{
/* Not expecting any type */
if (Xtra.expecting_type == XtraNil) {
XEvent ev = {
.xclient = {
.type = ClientMessage,
.display = e->display,
.window = e->data.l[0],
.message_type = XdndFinished,
.format = 32,
.data = {
.l = {Xtra.proxy_window, 0, 0}
}
}
};
XSendEvent(Xtra.display, e->data.l[0], False, NoEventMask, &ev);
} else {
Xtra.source_window = e->data.l[0];
XConvertSelection(Xtra.display,
XdndSelection,
Xtra.expecting_type,
XdndSelection,
Xtra.proxy_window,
Xtra.handling_version >= 1 ? e->data.l[2] : CurrentTime);
}
}
static void handle_xdnd_selection(XSelectionEvent *e)
{
UNUSED_VAR(e);
/* DnD succesfully finished, send finished and call callback */
XEvent ev = {
.xclient = {
.type = ClientMessage,
.display = Xtra.display,
.window = Xtra.source_window,
.message_type = XdndFinished,
.format = 32,
.data = {
.l = {Xtra.proxy_window, 1, XdndActionCopy}
}
}
};
XSendEvent(Xtra.display, Xtra.source_window, False, NoEventMask, &ev);
Property p = read_property(Xtra.proxy_window, XdndSelection);
DropType dt;
if (strcmp(XGetAtomName(Xtra.display, p.read_type), "text/uri-list") == 0) {
dt = DT_file_list;
} else { /* text/uri-list */
dt = DT_plain;
}
/* Call callback for every entry */
if (Xtra.on_drop && p.read_num) {
char *sptr;
char *str = strtok_r((char *) p.data, "\n\r", &sptr);
if (str) {
Xtra.on_drop(str, dt);
}
while ((str = strtok_r(NULL, "\n\r", &sptr))) {
Xtra.on_drop(str, dt);
}
}
if (p.data) {
XFree(p.data);
}
}
void *event_loop(void *p)
{
/* Handle events like a real nigga */
UNUSED_VAR(p); /* DINDUNOTHIN */
XEvent event;
int pending;
while (Xtra.display) {
/* NEEDMOEVENTSFODEMPROGRAMS */
XLockDisplay(Xtra.display);
if ((pending = XPending(Xtra.display))) {
XNextEvent(Xtra.display, &event);
}
if (!pending) {
XUnlockDisplay(Xtra.display);
sleep_thread(10000L);
continue;
}
if (event.type == ClientMessage) {
Atom type = event.xclient.message_type;
if (type == XdndEnter) {
handle_xdnd_enter(&event.xclient);
} else if (type == XdndPosition) {
handle_xdnd_position(&event.xclient);
} else if (type == XdndDrop) {
handle_xdnd_drop(&event.xclient);
} else if (type == XtraTerminate) {
break;
}
} else if (event.type == SelectionNotify) {
handle_xdnd_selection(&event.xselection);
}
/* AINNOBODYCANHANDLEDEMEVENTS*/
else {
XSendEvent(Xtra.display, Xtra.terminal_window, 0, 0, &event);
}
XUnlockDisplay(Xtra.display);
}
/* Actual XTRA termination
* Please call xtra_terminate() at exit
* otherwise HEWUSAGUDBOI happens
*/
if (Xtra.display) {
XCloseDisplay(Xtra.display);
}
return (Xtra.display = NULL);
}
static long unsigned int focused_window_id(void)
{
if (!Xtra.display) {
return 0;
}
Window focus;
int revert;
XLockDisplay(Xtra.display);
XGetInputFocus(Xtra.display, &focus, &revert);
XUnlockDisplay(Xtra.display);
return focus;
}
int is_focused(void)
{
return Xtra.display && (Xtra.proxy_window == focused_window_id() || Xtra.terminal_window == focused_window_id());
}
int init_xtra(drop_callback d)
{
if (!d) {
return -1;
} else {
Xtra.on_drop = d;
}
XInitThreads();
if (!(Xtra.display = XOpenDisplay(NULL))) {
return -1;
}
Xtra.terminal_window = focused_window_id();
/* OSX: if focused window is 0, it means toxic is ran from
* native terminal and not X11 terminal window, silently exit */
if (!Xtra.terminal_window) {
return 0;
}
{
/* Create an invisible window which will act as proxy for the DnD operation. */
XSetWindowAttributes attr = {0};
attr.event_mask = EnterWindowMask |
LeaveWindowMask |
ButtonMotionMask |
ButtonPressMask |
ButtonReleaseMask |
ResizeRedirectMask;
attr.do_not_propagate_mask = NoEventMask;
Window root;
int x, y;
unsigned int wht, hht, b, d;
/* Since we cannot capture resize events for parent window we will have to create
* this window to have maximum size as defined in root window
*/
XGetGeometry(Xtra.display,
XDefaultRootWindow(Xtra.display),
&root, &x, &y, &wht, &hht, &b, &d);
if (!(Xtra.proxy_window = XCreateWindow
(Xtra.display, Xtra.terminal_window, /* Parent */
0, 0, /* Position */
wht, hht, /* Width + height */
0, /* Border width */
CopyFromParent, /* Depth */
InputOnly, /* Class */
CopyFromParent, /* Visual */
CWEventMask | CWCursor, /* Value mask */
&attr))) { /* Attributes for value mask */
return -1;
}
}
XMapWindow(Xtra.display, Xtra.proxy_window); /* Show window (sandwich) */
XLowerWindow(Xtra.display, Xtra.proxy_window); /* Don't interfere with parent lmao */
XdndAware = XInternAtom(Xtra.display, "XdndAware", False);
XdndEnter = XInternAtom(Xtra.display, "XdndEnter", False);
XdndLeave = XInternAtom(Xtra.display, "XdndLeave", False);
XdndPosition = XInternAtom(Xtra.display, "XdndPosition", False);
XdndStatus = XInternAtom(Xtra.display, "XdndStatus", False);
XdndDrop = XInternAtom(Xtra.display, "XdndDrop", False);
XdndSelection = XInternAtom(Xtra.display, "XdndSelection", False);
XdndDATA = XInternAtom(Xtra.display, "XdndDATA", False);
XdndTypeList = XInternAtom(Xtra.display, "XdndTypeList", False);
XdndActionCopy = XInternAtom(Xtra.display, "XdndActionCopy", False);
XdndFinished = XInternAtom(Xtra.display, "XdndFinished", False);
/* Inform my nigga windows that we are aware of dnd */
Atom XdndVersion = 3;
XChangeProperty(Xtra.display,
Xtra.proxy_window,
XdndAware,
XA_ATOM,
32,
PropModeReplace,
(unsigned char *)&XdndVersion, 1);
pthread_t id;
if (pthread_create(&id, NULL, event_loop, NULL) != 0) {
return -1;
}
pthread_detach(id);
return 0;
}
void terminate_xtra(void)
{
if (!Xtra.display || !Xtra.terminal_window) {
return;
}
XEvent terminate = {
.xclient = {
.type = ClientMessage,
.display = Xtra.display,
.message_type = XtraTerminate,
}
};
XLockDisplay(Xtra.display);
XDeleteProperty(Xtra.display, Xtra.proxy_window, XdndAware);
XSendEvent(Xtra.display, Xtra.proxy_window, 0, NoEventMask, &terminate);
XUnlockDisplay(Xtra.display);
while (Xtra.display); /* Wait for termination */
}
#endif /* !__APPLE__ */