├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── assets ├── bg_icon_clouds.png ├── bg_icon_first_quarter.png ├── bg_icon_fog.png ├── bg_icon_full_moon.png ├── bg_icon_last_quarter.png ├── bg_icon_new_moon.png ├── bg_icon_rainfall.png ├── bg_icon_showers.png ├── bg_icon_snow.png ├── bg_icon_thunder.png ├── bg_night.png ├── bg_notclear_day.png ├── bg_sunny_day.png ├── clear.png ├── clouds.png ├── fog.png ├── fonts │ ├── NotoSans-Regular.ttf │ └── OFL.txt ├── mock_cloudy_day.xcf ├── mock_day.xcf ├── mock_night.xcf ├── rainfall.png ├── showers.png ├── snow.png └── thunder.png ├── deps ├── cJSON │ ├── LICENSE │ ├── cJSON.c │ └── cJSON.h └── stb_image.h ├── font.c ├── font.h ├── image.c ├── image.h ├── log.c ├── log.h ├── main.c ├── request.py ├── weather.c └── weather.h /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | *.swo 4 | *.o 5 | windy 6 | SDL_src/ 7 | build/ 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2023-2024 Davidson Francis 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | cmake_minimum_required(VERSION 3.11) 24 | 25 | project(windy C) 26 | set(CMAKE_C_STANDARD 99) 27 | 28 | add_executable(windy 29 | main.c 30 | weather.c 31 | font.c 32 | image.c 33 | log.c 34 | deps/cJSON/cJSON.c) 35 | 36 | target_compile_options(windy PRIVATE 37 | -Wall -Wextra) 38 | 39 | include(FetchContent) 40 | set(FETCHCONTENT_BASE_DIR ${CMAKE_SOURCE_DIR}/SDL_src) 41 | set(FETCHCONTENT_QUIET FALSE) 42 | 43 | # Set the libraries you want to find 44 | include(FindPkgConfig) 45 | set(PKGCONFIG_LIBRARIES sdl3 sdl3-ttf) 46 | 47 | # Define the specific GIT_TAG for each library 48 | set(GIT_TAGS 49 | 6cc9ce183d904489bf8e33e26b91d6012667e1b0 # SDL 50 | 0ad0c2518be948300ed1e133741932f1ba2a4e7b) # SDL_ttf 51 | 52 | # Define a mapping between pkg-config names and repository names 53 | set(GIT_REPOS SDL SDL_ttf) 54 | set(GIT_HASHES 55 | f164f590589a09d92922dbcd504daaa6 56 | 01f465502c14817f39aad2fc5f2a84f7 57 | ) 58 | 59 | # Link names 60 | set(LNK_NAMES SDL3::SDL3 SDL3_ttf::SDL3_ttf) 61 | 62 | # Find and set compiler and linker flags for each library using pkg-config 63 | foreach(INDEX RANGE 1) 64 | 65 | list(GET PKGCONFIG_LIBRARIES ${INDEX} LIBRARY) 66 | list(GET GIT_REPOS ${INDEX} REPO) 67 | list(GET GIT_HASHES ${INDEX} HASH) 68 | list(GET GIT_TAGS ${INDEX} TAG) 69 | list(GET LNK_NAMES ${INDEX} LINK) 70 | 71 | pkg_check_modules(PKG_${LIBRARY} QUIET IMPORTED_TARGET ${LIBRARY}) 72 | if (NOT PKG_${LIBRARY}_FOUND) 73 | message(WARNING 74 | "${LIBRARY} not found via pkg-config, trying to fetch manually...") 75 | 76 | FetchContent_Declare( 77 | "av_${LIBRARY}" 78 | URL https://github.com/libsdl-org/${REPO}/archive/${TAG}.zip 79 | URL_HASH MD5=${HASH} 80 | ) 81 | FetchContent_MakeAvailable("av_${LIBRARY}") 82 | 83 | # Add to our lib list 'normally' 84 | target_link_libraries(windy PUBLIC ${LINK}) 85 | else() 86 | # If found, add to our lib list via pkg-conig 87 | target_link_libraries(windy PUBLIC PkgConfig::PKG_${LIBRARY}) 88 | endif() 89 | endforeach() 90 | 91 | # Math lib 92 | target_link_libraries(windy PUBLIC m) 93 | 94 | # Copy assets folder to build folder 95 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets 96 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) 97 | 98 | # Copy request script to build folder 99 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/request.py 100 | DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Davidson Francis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2023 Davidson Francis 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | CC ?= gcc 24 | CFLAGS += `pkg-config --cflags sdl3` -O2 -Wall -Wextra 25 | LDFLAGS += `pkg-config --libs sdl3` -lSDL3_ttf -pthread -lm -ldl 26 | C_SRC = main.c font.c weather.c image.c log.c deps/cJSON/cJSON.c 27 | 28 | # Objects 29 | OBJ = $(C_SRC:.c=.o) 30 | 31 | # Build objects rule 32 | %.o: %.c Makefile 33 | $(CC) $< $(CFLAGS) -c -o $@ 34 | 35 | all: windy 36 | 37 | windy: $(OBJ) 38 | $(CC) $(OBJ) -o $@ $(LDFLAGS) 39 | 40 | clean: 41 | rm -f $(OBJ) 42 | rm -f windy 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🍃 windy 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-8affa5.svg)](https://opensource.org/licenses/MIT) 3 | 4 | An SDL3 widget that attempts to looks like the good ol' Windows Vista/7 weather 5 | widget 6 | 7 | ## Introduction 8 | Even though I'm a Linux user 99% of the time, one of the few things I miss is 9 | the weather widget from Windows Vista/7: it's simple, but I genuinely find it 10 | attractive and useful. 11 | 12 | While there are various weather widgets for Linux using Conky, I've never come 13 | across one that resembled the Windows 7 version. So, I decided to create my own 14 | with C and SDL3. Windy is open source and works on Linux, FreeBSD, MacOS, and 15 | Windows. 16 | 17 | Windy: 18 |

19 | Windy examples 20 |
21 | Windy examples 22 |

23 | 24 | 25 | ## How to use 26 | ### Quick Explanation: 27 | 28 | Change the following lines of `request.py` file with the lat/long of your city 29 | (exact location not needed!): 30 | ```python 31 | # 32 | # Example: 33 | # LATITUDE="35.69" 34 | # LONGITUDE="139.69" 35 | # LOCATION="Tokyo, Japan" 36 | # 37 | LATITUDE="" 38 | LONGITUDE="" 39 | LOCATION="" 40 | ``` 41 | 42 | and then run: 43 | ```c 44 | $ ./windy -c "python request.py" 45 | ``` 46 | 47 | ### Detailed Explanation: 48 | 49 | Unlike most weather programs/widgets, Windy doesn't attempt to embed one or more 50 | forecast APIs within itself. Instead, it delegates this responsibility to an 51 | external program/script. 52 | 53 | Windy accomplishes this by invoking an external script provided as a parameter by 54 | the user. This script returns JSON output through stdout, which Windy reads and 55 | displays on the screen. Essentially, Windy serves as a GUI for an external 56 | program. 57 | 58 | By default, Windy provides the `request.py` script, which uses the 59 | [OpenMeteo's API](https://open-meteo.com/en/docs) to fetch weather information. 60 | However, users can supply any script, program, etc., in their preferred language, 61 | as long as it follows the JSON format below: 62 | ```json 63 | { 64 | "temperature": 22, 65 | "condition": "clear", 66 | "max_temp": 20, 67 | "min_temp": 15, 68 | "location": "Tokyo, Japan", 69 | "provider": "OpenMeteo", 70 | "forecast": [ 71 | { 72 | "max_temp": 34, 73 | "min_temp": 27, 74 | "condition": "rainfall" 75 | }, 76 | { 77 | "max_temp": 34, 78 | "min_temp": 27, 79 | "condition": "clouds" 80 | }, 81 | { 82 | "max_temp": 34, 83 | "min_temp": 27, 84 | "condition": "clouds" 85 | } 86 | ] 87 | } 88 | 89 | ``` 90 | acceptable values for `condition` are: `clear`, `fog`, `clouds`, `showers`, 91 | `rainfall`, `thunder`, and `snow`. 92 | 93 | ### Command-line arguments: 94 | 95 | Windy also supports changing the weather update interval (`-t`) and the screen 96 | coordinates (`-x` and `-y`) where it should appear. Below are all the available 97 | options: 98 | ```text 99 | $ ./windy -h 100 | Usage: ./windy [options] -c 101 | Options: 102 | -t Interval time (in seconds) to check for weather 103 | updates (default = 10 minutes) 104 | -c Command to execute when the update time reaches 105 | -x Set the window X coordinate 106 | -y Set the window Y coordinate 107 | -h This help 108 | 109 | Example: 110 | Update the weather info each 30 minutes, by running the command 111 | 'python request.py' 112 | $ ./windy -t 1800 -c "python request.py" 113 | 114 | Obs: Options -t,-x and -y are not required, -c is required! 115 | ``` 116 | 117 | ## Building 118 | Windy requires `SDL3`[^sdl3_note] and `SDL3_ttf` to build, if you don't have 119 | them installed and your distro/OS doesn't have packages for them (highly likely, 120 | as they're still under development) build with CMake: 121 | ```bash 122 | $ git clone https://github.com/Theldus/windy.git 123 | $ cd windy 124 | $ mkdir build 125 | $ cd build 126 | $ cmake .. 127 | $ make -j4 128 | ``` 129 | CMake will check if the libs can be found on the system, and if not, it will 130 | download and build them. 131 | 132 | Optionally, if you are running Linux and already have the libraries installed on 133 | your system, just a 'make' is enough: 134 | ```bash 135 | $ git clone https://github.com/Theldus/windy.git 136 | $ cd windy 137 | $ make -j4 138 | ``` 139 | 140 | [^sdl3_note]: SDL3 is the first version of SDL to support transparent framebuffer, 141 | which is why version 3 is required. 142 | 143 | ## Contributing 144 | Windy is always open to the community and willing to accept contributions, 145 | whether with issues, documentation, testing, new features, bugfixes, typos, and 146 | etc. Welcome aboard. 147 | 148 | ## License 149 | Windy is licensed under the [MIT license](https://opensource.org/license/mit/). 150 | 151 | Weather icons were created by 152 | [gnokii](https://www.deviantart.com/gnokii/art/Weather-Icon-Set-308364079) and 153 | are licensed under the 154 | [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/). 155 | 156 | NotoSans font was created by Google and is licensed under the 157 | [OFL license](https://opensource.org/license/ofl-1-1/). 158 | -------------------------------------------------------------------------------- /assets/bg_icon_clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_clouds.png -------------------------------------------------------------------------------- /assets/bg_icon_first_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_first_quarter.png -------------------------------------------------------------------------------- /assets/bg_icon_fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_fog.png -------------------------------------------------------------------------------- /assets/bg_icon_full_moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_full_moon.png -------------------------------------------------------------------------------- /assets/bg_icon_last_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_last_quarter.png -------------------------------------------------------------------------------- /assets/bg_icon_new_moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_new_moon.png -------------------------------------------------------------------------------- /assets/bg_icon_rainfall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_rainfall.png -------------------------------------------------------------------------------- /assets/bg_icon_showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_showers.png -------------------------------------------------------------------------------- /assets/bg_icon_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_snow.png -------------------------------------------------------------------------------- /assets/bg_icon_thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_icon_thunder.png -------------------------------------------------------------------------------- /assets/bg_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_night.png -------------------------------------------------------------------------------- /assets/bg_notclear_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_notclear_day.png -------------------------------------------------------------------------------- /assets/bg_sunny_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/bg_sunny_day.png -------------------------------------------------------------------------------- /assets/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/clear.png -------------------------------------------------------------------------------- /assets/clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/clouds.png -------------------------------------------------------------------------------- /assets/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/fog.png -------------------------------------------------------------------------------- /assets/fonts/NotoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/fonts/NotoSans-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2015-2021 Google LLC. All Rights Reserved. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /assets/mock_cloudy_day.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/mock_cloudy_day.xcf -------------------------------------------------------------------------------- /assets/mock_day.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/mock_day.xcf -------------------------------------------------------------------------------- /assets/mock_night.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/mock_night.xcf -------------------------------------------------------------------------------- /assets/rainfall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/rainfall.png -------------------------------------------------------------------------------- /assets/showers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/showers.png -------------------------------------------------------------------------------- /assets/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/snow.png -------------------------------------------------------------------------------- /assets/thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theldus/windy/0d4678fa956ea94ecbff2c0f4b143b3b574d10c8/assets/thunder.png -------------------------------------------------------------------------------- /deps/cJSON/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2017 Dave Gamble and cJSON contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /deps/cJSON/cJSON.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2017 Dave Gamble and cJSON contributors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | /* cJSON */ 24 | /* JSON parser in C. */ 25 | 26 | /* disable warnings about old C89 functions in MSVC */ 27 | #if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) 28 | #define _CRT_SECURE_NO_DEPRECATE 29 | #endif 30 | 31 | #ifdef __GNUC__ 32 | #pragma GCC visibility push(default) 33 | #endif 34 | #if defined(_MSC_VER) 35 | #pragma warning (push) 36 | /* disable warning about single line comments in system headers */ 37 | #pragma warning (disable : 4001) 38 | #endif 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #ifdef ENABLE_LOCALES 49 | #include 50 | #endif 51 | 52 | #if defined(_MSC_VER) 53 | #pragma warning (pop) 54 | #endif 55 | #ifdef __GNUC__ 56 | #pragma GCC visibility pop 57 | #endif 58 | 59 | #include "cJSON.h" 60 | 61 | /* define our own boolean type */ 62 | #ifdef true 63 | #undef true 64 | #endif 65 | #define true ((cJSON_bool)1) 66 | 67 | #ifdef false 68 | #undef false 69 | #endif 70 | #define false ((cJSON_bool)0) 71 | 72 | /* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ 73 | #ifndef isinf 74 | #define isinf(d) (isnan((d - d)) && !isnan(d)) 75 | #endif 76 | #ifndef isnan 77 | #define isnan(d) (d != d) 78 | #endif 79 | 80 | #ifndef NAN 81 | #ifdef _WIN32 82 | #define NAN sqrt(-1.0) 83 | #else 84 | #define NAN 0.0/0.0 85 | #endif 86 | #endif 87 | 88 | typedef struct { 89 | const unsigned char *json; 90 | size_t position; 91 | } error; 92 | static error global_error = { NULL, 0 }; 93 | 94 | CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) 95 | { 96 | return (const char*) (global_error.json + global_error.position); 97 | } 98 | 99 | CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) 100 | { 101 | if (!cJSON_IsString(item)) 102 | { 103 | return NULL; 104 | } 105 | 106 | return item->valuestring; 107 | } 108 | 109 | CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) 110 | { 111 | if (!cJSON_IsNumber(item)) 112 | { 113 | return (double) NAN; 114 | } 115 | 116 | return item->valuedouble; 117 | } 118 | 119 | /* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ 120 | #if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 16) 121 | #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. 122 | #endif 123 | 124 | CJSON_PUBLIC(const char*) cJSON_Version(void) 125 | { 126 | static char version[15]; 127 | sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); 128 | 129 | return version; 130 | } 131 | 132 | /* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ 133 | static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) 134 | { 135 | if ((string1 == NULL) || (string2 == NULL)) 136 | { 137 | return 1; 138 | } 139 | 140 | if (string1 == string2) 141 | { 142 | return 0; 143 | } 144 | 145 | for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) 146 | { 147 | if (*string1 == '\0') 148 | { 149 | return 0; 150 | } 151 | } 152 | 153 | return tolower(*string1) - tolower(*string2); 154 | } 155 | 156 | typedef struct internal_hooks 157 | { 158 | void *(CJSON_CDECL *allocate)(size_t size); 159 | void (CJSON_CDECL *deallocate)(void *pointer); 160 | void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); 161 | } internal_hooks; 162 | 163 | #if defined(_MSC_VER) 164 | /* work around MSVC error C2322: '...' address of dllimport '...' is not static */ 165 | static void * CJSON_CDECL internal_malloc(size_t size) 166 | { 167 | return malloc(size); 168 | } 169 | static void CJSON_CDECL internal_free(void *pointer) 170 | { 171 | free(pointer); 172 | } 173 | static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) 174 | { 175 | return realloc(pointer, size); 176 | } 177 | #else 178 | #define internal_malloc malloc 179 | #define internal_free free 180 | #define internal_realloc realloc 181 | #endif 182 | 183 | /* strlen of character literals resolved at compile time */ 184 | #define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) 185 | 186 | static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; 187 | 188 | static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) 189 | { 190 | size_t length = 0; 191 | unsigned char *copy = NULL; 192 | 193 | if (string == NULL) 194 | { 195 | return NULL; 196 | } 197 | 198 | length = strlen((const char*)string) + sizeof(""); 199 | copy = (unsigned char*)hooks->allocate(length); 200 | if (copy == NULL) 201 | { 202 | return NULL; 203 | } 204 | memcpy(copy, string, length); 205 | 206 | return copy; 207 | } 208 | 209 | CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) 210 | { 211 | if (hooks == NULL) 212 | { 213 | /* Reset hooks */ 214 | global_hooks.allocate = malloc; 215 | global_hooks.deallocate = free; 216 | global_hooks.reallocate = realloc; 217 | return; 218 | } 219 | 220 | global_hooks.allocate = malloc; 221 | if (hooks->malloc_fn != NULL) 222 | { 223 | global_hooks.allocate = hooks->malloc_fn; 224 | } 225 | 226 | global_hooks.deallocate = free; 227 | if (hooks->free_fn != NULL) 228 | { 229 | global_hooks.deallocate = hooks->free_fn; 230 | } 231 | 232 | /* use realloc only if both free and malloc are used */ 233 | global_hooks.reallocate = NULL; 234 | if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) 235 | { 236 | global_hooks.reallocate = realloc; 237 | } 238 | } 239 | 240 | /* Internal constructor. */ 241 | static cJSON *cJSON_New_Item(const internal_hooks * const hooks) 242 | { 243 | cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); 244 | if (node) 245 | { 246 | memset(node, '\0', sizeof(cJSON)); 247 | } 248 | 249 | return node; 250 | } 251 | 252 | /* Delete a cJSON structure. */ 253 | CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) 254 | { 255 | cJSON *next = NULL; 256 | while (item != NULL) 257 | { 258 | next = item->next; 259 | if (!(item->type & cJSON_IsReference) && (item->child != NULL)) 260 | { 261 | cJSON_Delete(item->child); 262 | } 263 | if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) 264 | { 265 | global_hooks.deallocate(item->valuestring); 266 | } 267 | if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) 268 | { 269 | global_hooks.deallocate(item->string); 270 | } 271 | global_hooks.deallocate(item); 272 | item = next; 273 | } 274 | } 275 | 276 | /* get the decimal point character of the current locale */ 277 | static unsigned char get_decimal_point(void) 278 | { 279 | #ifdef ENABLE_LOCALES 280 | struct lconv *lconv = localeconv(); 281 | return (unsigned char) lconv->decimal_point[0]; 282 | #else 283 | return '.'; 284 | #endif 285 | } 286 | 287 | typedef struct 288 | { 289 | const unsigned char *content; 290 | size_t length; 291 | size_t offset; 292 | size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ 293 | internal_hooks hooks; 294 | } parse_buffer; 295 | 296 | /* check if the given size is left to read in a given parse buffer (starting with 1) */ 297 | #define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) 298 | /* check if the buffer can be accessed at the given index (starting with 0) */ 299 | #define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) 300 | #define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) 301 | /* get a pointer to the buffer at the position */ 302 | #define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) 303 | 304 | /* Parse the input text to generate a number, and populate the result into item. */ 305 | static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) 306 | { 307 | double number = 0; 308 | unsigned char *after_end = NULL; 309 | unsigned char number_c_string[64]; 310 | unsigned char decimal_point = get_decimal_point(); 311 | size_t i = 0; 312 | 313 | if ((input_buffer == NULL) || (input_buffer->content == NULL)) 314 | { 315 | return false; 316 | } 317 | 318 | /* copy the number into a temporary buffer and replace '.' with the decimal point 319 | * of the current locale (for strtod) 320 | * This also takes care of '\0' not necessarily being available for marking the end of the input */ 321 | for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) 322 | { 323 | switch (buffer_at_offset(input_buffer)[i]) 324 | { 325 | case '0': 326 | case '1': 327 | case '2': 328 | case '3': 329 | case '4': 330 | case '5': 331 | case '6': 332 | case '7': 333 | case '8': 334 | case '9': 335 | case '+': 336 | case '-': 337 | case 'e': 338 | case 'E': 339 | number_c_string[i] = buffer_at_offset(input_buffer)[i]; 340 | break; 341 | 342 | case '.': 343 | number_c_string[i] = decimal_point; 344 | break; 345 | 346 | default: 347 | goto loop_end; 348 | } 349 | } 350 | loop_end: 351 | number_c_string[i] = '\0'; 352 | 353 | number = strtod((const char*)number_c_string, (char**)&after_end); 354 | if (number_c_string == after_end) 355 | { 356 | return false; /* parse_error */ 357 | } 358 | 359 | item->valuedouble = number; 360 | 361 | /* use saturation in case of overflow */ 362 | if (number >= INT_MAX) 363 | { 364 | item->valueint = INT_MAX; 365 | } 366 | else if (number <= (double)INT_MIN) 367 | { 368 | item->valueint = INT_MIN; 369 | } 370 | else 371 | { 372 | item->valueint = (int)number; 373 | } 374 | 375 | item->type = cJSON_Number; 376 | 377 | input_buffer->offset += (size_t)(after_end - number_c_string); 378 | return true; 379 | } 380 | 381 | /* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ 382 | CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) 383 | { 384 | if (number >= INT_MAX) 385 | { 386 | object->valueint = INT_MAX; 387 | } 388 | else if (number <= (double)INT_MIN) 389 | { 390 | object->valueint = INT_MIN; 391 | } 392 | else 393 | { 394 | object->valueint = (int)number; 395 | } 396 | 397 | return object->valuedouble = number; 398 | } 399 | 400 | CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) 401 | { 402 | char *copy = NULL; 403 | /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ 404 | if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) 405 | { 406 | return NULL; 407 | } 408 | if (strlen(valuestring) <= strlen(object->valuestring)) 409 | { 410 | strcpy(object->valuestring, valuestring); 411 | return object->valuestring; 412 | } 413 | copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); 414 | if (copy == NULL) 415 | { 416 | return NULL; 417 | } 418 | if (object->valuestring != NULL) 419 | { 420 | cJSON_free(object->valuestring); 421 | } 422 | object->valuestring = copy; 423 | 424 | return copy; 425 | } 426 | 427 | typedef struct 428 | { 429 | unsigned char *buffer; 430 | size_t length; 431 | size_t offset; 432 | size_t depth; /* current nesting depth (for formatted printing) */ 433 | cJSON_bool noalloc; 434 | cJSON_bool format; /* is this print a formatted print */ 435 | internal_hooks hooks; 436 | } printbuffer; 437 | 438 | /* realloc printbuffer if necessary to have at least "needed" bytes more */ 439 | static unsigned char* ensure(printbuffer * const p, size_t needed) 440 | { 441 | unsigned char *newbuffer = NULL; 442 | size_t newsize = 0; 443 | 444 | if ((p == NULL) || (p->buffer == NULL)) 445 | { 446 | return NULL; 447 | } 448 | 449 | if ((p->length > 0) && (p->offset >= p->length)) 450 | { 451 | /* make sure that offset is valid */ 452 | return NULL; 453 | } 454 | 455 | if (needed > INT_MAX) 456 | { 457 | /* sizes bigger than INT_MAX are currently not supported */ 458 | return NULL; 459 | } 460 | 461 | needed += p->offset + 1; 462 | if (needed <= p->length) 463 | { 464 | return p->buffer + p->offset; 465 | } 466 | 467 | if (p->noalloc) { 468 | return NULL; 469 | } 470 | 471 | /* calculate new buffer size */ 472 | if (needed > (INT_MAX / 2)) 473 | { 474 | /* overflow of int, use INT_MAX if possible */ 475 | if (needed <= INT_MAX) 476 | { 477 | newsize = INT_MAX; 478 | } 479 | else 480 | { 481 | return NULL; 482 | } 483 | } 484 | else 485 | { 486 | newsize = needed * 2; 487 | } 488 | 489 | if (p->hooks.reallocate != NULL) 490 | { 491 | /* reallocate with realloc if available */ 492 | newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); 493 | if (newbuffer == NULL) 494 | { 495 | p->hooks.deallocate(p->buffer); 496 | p->length = 0; 497 | p->buffer = NULL; 498 | 499 | return NULL; 500 | } 501 | } 502 | else 503 | { 504 | /* otherwise reallocate manually */ 505 | newbuffer = (unsigned char*)p->hooks.allocate(newsize); 506 | if (!newbuffer) 507 | { 508 | p->hooks.deallocate(p->buffer); 509 | p->length = 0; 510 | p->buffer = NULL; 511 | 512 | return NULL; 513 | } 514 | 515 | memcpy(newbuffer, p->buffer, p->offset + 1); 516 | p->hooks.deallocate(p->buffer); 517 | } 518 | p->length = newsize; 519 | p->buffer = newbuffer; 520 | 521 | return newbuffer + p->offset; 522 | } 523 | 524 | /* calculate the new length of the string in a printbuffer and update the offset */ 525 | static void update_offset(printbuffer * const buffer) 526 | { 527 | const unsigned char *buffer_pointer = NULL; 528 | if ((buffer == NULL) || (buffer->buffer == NULL)) 529 | { 530 | return; 531 | } 532 | buffer_pointer = buffer->buffer + buffer->offset; 533 | 534 | buffer->offset += strlen((const char*)buffer_pointer); 535 | } 536 | 537 | /* securely comparison of floating-point variables */ 538 | static cJSON_bool compare_double(double a, double b) 539 | { 540 | double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); 541 | return (fabs(a - b) <= maxVal * DBL_EPSILON); 542 | } 543 | 544 | /* Render the number nicely from the given item into a string. */ 545 | static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) 546 | { 547 | unsigned char *output_pointer = NULL; 548 | double d = item->valuedouble; 549 | int length = 0; 550 | size_t i = 0; 551 | unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ 552 | unsigned char decimal_point = get_decimal_point(); 553 | double test = 0.0; 554 | 555 | if (output_buffer == NULL) 556 | { 557 | return false; 558 | } 559 | 560 | /* This checks for NaN and Infinity */ 561 | if (isnan(d) || isinf(d)) 562 | { 563 | length = sprintf((char*)number_buffer, "null"); 564 | } 565 | else if(d == (double)item->valueint) 566 | { 567 | length = sprintf((char*)number_buffer, "%d", item->valueint); 568 | } 569 | else 570 | { 571 | /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ 572 | length = sprintf((char*)number_buffer, "%1.15g", d); 573 | 574 | /* Check whether the original double can be recovered */ 575 | if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) 576 | { 577 | /* If not, print with 17 decimal places of precision */ 578 | length = sprintf((char*)number_buffer, "%1.17g", d); 579 | } 580 | } 581 | 582 | /* sprintf failed or buffer overrun occurred */ 583 | if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) 584 | { 585 | return false; 586 | } 587 | 588 | /* reserve appropriate space in the output */ 589 | output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); 590 | if (output_pointer == NULL) 591 | { 592 | return false; 593 | } 594 | 595 | /* copy the printed number to the output and replace locale 596 | * dependent decimal point with '.' */ 597 | for (i = 0; i < ((size_t)length); i++) 598 | { 599 | if (number_buffer[i] == decimal_point) 600 | { 601 | output_pointer[i] = '.'; 602 | continue; 603 | } 604 | 605 | output_pointer[i] = number_buffer[i]; 606 | } 607 | output_pointer[i] = '\0'; 608 | 609 | output_buffer->offset += (size_t)length; 610 | 611 | return true; 612 | } 613 | 614 | /* parse 4 digit hexadecimal number */ 615 | static unsigned parse_hex4(const unsigned char * const input) 616 | { 617 | unsigned int h = 0; 618 | size_t i = 0; 619 | 620 | for (i = 0; i < 4; i++) 621 | { 622 | /* parse digit */ 623 | if ((input[i] >= '0') && (input[i] <= '9')) 624 | { 625 | h += (unsigned int) input[i] - '0'; 626 | } 627 | else if ((input[i] >= 'A') && (input[i] <= 'F')) 628 | { 629 | h += (unsigned int) 10 + input[i] - 'A'; 630 | } 631 | else if ((input[i] >= 'a') && (input[i] <= 'f')) 632 | { 633 | h += (unsigned int) 10 + input[i] - 'a'; 634 | } 635 | else /* invalid */ 636 | { 637 | return 0; 638 | } 639 | 640 | if (i < 3) 641 | { 642 | /* shift left to make place for the next nibble */ 643 | h = h << 4; 644 | } 645 | } 646 | 647 | return h; 648 | } 649 | 650 | /* converts a UTF-16 literal to UTF-8 651 | * A literal can be one or two sequences of the form \uXXXX */ 652 | static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) 653 | { 654 | long unsigned int codepoint = 0; 655 | unsigned int first_code = 0; 656 | const unsigned char *first_sequence = input_pointer; 657 | unsigned char utf8_length = 0; 658 | unsigned char utf8_position = 0; 659 | unsigned char sequence_length = 0; 660 | unsigned char first_byte_mark = 0; 661 | 662 | if ((input_end - first_sequence) < 6) 663 | { 664 | /* input ends unexpectedly */ 665 | goto fail; 666 | } 667 | 668 | /* get the first utf16 sequence */ 669 | first_code = parse_hex4(first_sequence + 2); 670 | 671 | /* check that the code is valid */ 672 | if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) 673 | { 674 | goto fail; 675 | } 676 | 677 | /* UTF16 surrogate pair */ 678 | if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) 679 | { 680 | const unsigned char *second_sequence = first_sequence + 6; 681 | unsigned int second_code = 0; 682 | sequence_length = 12; /* \uXXXX\uXXXX */ 683 | 684 | if ((input_end - second_sequence) < 6) 685 | { 686 | /* input ends unexpectedly */ 687 | goto fail; 688 | } 689 | 690 | if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) 691 | { 692 | /* missing second half of the surrogate pair */ 693 | goto fail; 694 | } 695 | 696 | /* get the second utf16 sequence */ 697 | second_code = parse_hex4(second_sequence + 2); 698 | /* check that the code is valid */ 699 | if ((second_code < 0xDC00) || (second_code > 0xDFFF)) 700 | { 701 | /* invalid second half of the surrogate pair */ 702 | goto fail; 703 | } 704 | 705 | 706 | /* calculate the unicode codepoint from the surrogate pair */ 707 | codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); 708 | } 709 | else 710 | { 711 | sequence_length = 6; /* \uXXXX */ 712 | codepoint = first_code; 713 | } 714 | 715 | /* encode as UTF-8 716 | * takes at maximum 4 bytes to encode: 717 | * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ 718 | if (codepoint < 0x80) 719 | { 720 | /* normal ascii, encoding 0xxxxxxx */ 721 | utf8_length = 1; 722 | } 723 | else if (codepoint < 0x800) 724 | { 725 | /* two bytes, encoding 110xxxxx 10xxxxxx */ 726 | utf8_length = 2; 727 | first_byte_mark = 0xC0; /* 11000000 */ 728 | } 729 | else if (codepoint < 0x10000) 730 | { 731 | /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ 732 | utf8_length = 3; 733 | first_byte_mark = 0xE0; /* 11100000 */ 734 | } 735 | else if (codepoint <= 0x10FFFF) 736 | { 737 | /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ 738 | utf8_length = 4; 739 | first_byte_mark = 0xF0; /* 11110000 */ 740 | } 741 | else 742 | { 743 | /* invalid unicode codepoint */ 744 | goto fail; 745 | } 746 | 747 | /* encode as utf8 */ 748 | for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) 749 | { 750 | /* 10xxxxxx */ 751 | (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); 752 | codepoint >>= 6; 753 | } 754 | /* encode first byte */ 755 | if (utf8_length > 1) 756 | { 757 | (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); 758 | } 759 | else 760 | { 761 | (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); 762 | } 763 | 764 | *output_pointer += utf8_length; 765 | 766 | return sequence_length; 767 | 768 | fail: 769 | return 0; 770 | } 771 | 772 | /* Parse the input text into an unescaped cinput, and populate item. */ 773 | static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) 774 | { 775 | const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; 776 | const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; 777 | unsigned char *output_pointer = NULL; 778 | unsigned char *output = NULL; 779 | 780 | /* not a string */ 781 | if (buffer_at_offset(input_buffer)[0] != '\"') 782 | { 783 | goto fail; 784 | } 785 | 786 | { 787 | /* calculate approximate size of the output (overestimate) */ 788 | size_t allocation_length = 0; 789 | size_t skipped_bytes = 0; 790 | while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) 791 | { 792 | /* is escape sequence */ 793 | if (input_end[0] == '\\') 794 | { 795 | if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) 796 | { 797 | /* prevent buffer overflow when last input character is a backslash */ 798 | goto fail; 799 | } 800 | skipped_bytes++; 801 | input_end++; 802 | } 803 | input_end++; 804 | } 805 | if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) 806 | { 807 | goto fail; /* string ended unexpectedly */ 808 | } 809 | 810 | /* This is at most how much we need for the output */ 811 | allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; 812 | output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); 813 | if (output == NULL) 814 | { 815 | goto fail; /* allocation failure */ 816 | } 817 | } 818 | 819 | output_pointer = output; 820 | /* loop through the string literal */ 821 | while (input_pointer < input_end) 822 | { 823 | if (*input_pointer != '\\') 824 | { 825 | *output_pointer++ = *input_pointer++; 826 | } 827 | /* escape sequence */ 828 | else 829 | { 830 | unsigned char sequence_length = 2; 831 | if ((input_end - input_pointer) < 1) 832 | { 833 | goto fail; 834 | } 835 | 836 | switch (input_pointer[1]) 837 | { 838 | case 'b': 839 | *output_pointer++ = '\b'; 840 | break; 841 | case 'f': 842 | *output_pointer++ = '\f'; 843 | break; 844 | case 'n': 845 | *output_pointer++ = '\n'; 846 | break; 847 | case 'r': 848 | *output_pointer++ = '\r'; 849 | break; 850 | case 't': 851 | *output_pointer++ = '\t'; 852 | break; 853 | case '\"': 854 | case '\\': 855 | case '/': 856 | *output_pointer++ = input_pointer[1]; 857 | break; 858 | 859 | /* UTF-16 literal */ 860 | case 'u': 861 | sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); 862 | if (sequence_length == 0) 863 | { 864 | /* failed to convert UTF16-literal to UTF-8 */ 865 | goto fail; 866 | } 867 | break; 868 | 869 | default: 870 | goto fail; 871 | } 872 | input_pointer += sequence_length; 873 | } 874 | } 875 | 876 | /* zero terminate the output */ 877 | *output_pointer = '\0'; 878 | 879 | item->type = cJSON_String; 880 | item->valuestring = (char*)output; 881 | 882 | input_buffer->offset = (size_t) (input_end - input_buffer->content); 883 | input_buffer->offset++; 884 | 885 | return true; 886 | 887 | fail: 888 | if (output != NULL) 889 | { 890 | input_buffer->hooks.deallocate(output); 891 | } 892 | 893 | if (input_pointer != NULL) 894 | { 895 | input_buffer->offset = (size_t)(input_pointer - input_buffer->content); 896 | } 897 | 898 | return false; 899 | } 900 | 901 | /* Render the cstring provided to an escaped version that can be printed. */ 902 | static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) 903 | { 904 | const unsigned char *input_pointer = NULL; 905 | unsigned char *output = NULL; 906 | unsigned char *output_pointer = NULL; 907 | size_t output_length = 0; 908 | /* numbers of additional characters needed for escaping */ 909 | size_t escape_characters = 0; 910 | 911 | if (output_buffer == NULL) 912 | { 913 | return false; 914 | } 915 | 916 | /* empty string */ 917 | if (input == NULL) 918 | { 919 | output = ensure(output_buffer, sizeof("\"\"")); 920 | if (output == NULL) 921 | { 922 | return false; 923 | } 924 | strcpy((char*)output, "\"\""); 925 | 926 | return true; 927 | } 928 | 929 | /* set "flag" to 1 if something needs to be escaped */ 930 | for (input_pointer = input; *input_pointer; input_pointer++) 931 | { 932 | switch (*input_pointer) 933 | { 934 | case '\"': 935 | case '\\': 936 | case '\b': 937 | case '\f': 938 | case '\n': 939 | case '\r': 940 | case '\t': 941 | /* one character escape sequence */ 942 | escape_characters++; 943 | break; 944 | default: 945 | if (*input_pointer < 32) 946 | { 947 | /* UTF-16 escape sequence uXXXX */ 948 | escape_characters += 5; 949 | } 950 | break; 951 | } 952 | } 953 | output_length = (size_t)(input_pointer - input) + escape_characters; 954 | 955 | output = ensure(output_buffer, output_length + sizeof("\"\"")); 956 | if (output == NULL) 957 | { 958 | return false; 959 | } 960 | 961 | /* no characters have to be escaped */ 962 | if (escape_characters == 0) 963 | { 964 | output[0] = '\"'; 965 | memcpy(output + 1, input, output_length); 966 | output[output_length + 1] = '\"'; 967 | output[output_length + 2] = '\0'; 968 | 969 | return true; 970 | } 971 | 972 | output[0] = '\"'; 973 | output_pointer = output + 1; 974 | /* copy the string */ 975 | for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) 976 | { 977 | if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) 978 | { 979 | /* normal character, copy */ 980 | *output_pointer = *input_pointer; 981 | } 982 | else 983 | { 984 | /* character needs to be escaped */ 985 | *output_pointer++ = '\\'; 986 | switch (*input_pointer) 987 | { 988 | case '\\': 989 | *output_pointer = '\\'; 990 | break; 991 | case '\"': 992 | *output_pointer = '\"'; 993 | break; 994 | case '\b': 995 | *output_pointer = 'b'; 996 | break; 997 | case '\f': 998 | *output_pointer = 'f'; 999 | break; 1000 | case '\n': 1001 | *output_pointer = 'n'; 1002 | break; 1003 | case '\r': 1004 | *output_pointer = 'r'; 1005 | break; 1006 | case '\t': 1007 | *output_pointer = 't'; 1008 | break; 1009 | default: 1010 | /* escape and print as unicode codepoint */ 1011 | sprintf((char*)output_pointer, "u%04x", *input_pointer); 1012 | output_pointer += 4; 1013 | break; 1014 | } 1015 | } 1016 | } 1017 | output[output_length + 1] = '\"'; 1018 | output[output_length + 2] = '\0'; 1019 | 1020 | return true; 1021 | } 1022 | 1023 | /* Invoke print_string_ptr (which is useful) on an item. */ 1024 | static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) 1025 | { 1026 | return print_string_ptr((unsigned char*)item->valuestring, p); 1027 | } 1028 | 1029 | /* Predeclare these prototypes. */ 1030 | static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); 1031 | static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); 1032 | static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); 1033 | static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); 1034 | static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); 1035 | static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); 1036 | 1037 | /* Utility to jump whitespace and cr/lf */ 1038 | static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) 1039 | { 1040 | if ((buffer == NULL) || (buffer->content == NULL)) 1041 | { 1042 | return NULL; 1043 | } 1044 | 1045 | if (cannot_access_at_index(buffer, 0)) 1046 | { 1047 | return buffer; 1048 | } 1049 | 1050 | while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) 1051 | { 1052 | buffer->offset++; 1053 | } 1054 | 1055 | if (buffer->offset == buffer->length) 1056 | { 1057 | buffer->offset--; 1058 | } 1059 | 1060 | return buffer; 1061 | } 1062 | 1063 | /* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ 1064 | static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) 1065 | { 1066 | if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) 1067 | { 1068 | return NULL; 1069 | } 1070 | 1071 | if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) 1072 | { 1073 | buffer->offset += 3; 1074 | } 1075 | 1076 | return buffer; 1077 | } 1078 | 1079 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) 1080 | { 1081 | size_t buffer_length; 1082 | 1083 | if (NULL == value) 1084 | { 1085 | return NULL; 1086 | } 1087 | 1088 | /* Adding null character size due to require_null_terminated. */ 1089 | buffer_length = strlen(value) + sizeof(""); 1090 | 1091 | return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); 1092 | } 1093 | 1094 | /* Parse an object - create a new root, and populate. */ 1095 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) 1096 | { 1097 | parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; 1098 | cJSON *item = NULL; 1099 | 1100 | /* reset error position */ 1101 | global_error.json = NULL; 1102 | global_error.position = 0; 1103 | 1104 | if (value == NULL || 0 == buffer_length) 1105 | { 1106 | goto fail; 1107 | } 1108 | 1109 | buffer.content = (const unsigned char*)value; 1110 | buffer.length = buffer_length; 1111 | buffer.offset = 0; 1112 | buffer.hooks = global_hooks; 1113 | 1114 | item = cJSON_New_Item(&global_hooks); 1115 | if (item == NULL) /* memory fail */ 1116 | { 1117 | goto fail; 1118 | } 1119 | 1120 | if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) 1121 | { 1122 | /* parse failure. ep is set. */ 1123 | goto fail; 1124 | } 1125 | 1126 | /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ 1127 | if (require_null_terminated) 1128 | { 1129 | buffer_skip_whitespace(&buffer); 1130 | if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') 1131 | { 1132 | goto fail; 1133 | } 1134 | } 1135 | if (return_parse_end) 1136 | { 1137 | *return_parse_end = (const char*)buffer_at_offset(&buffer); 1138 | } 1139 | 1140 | return item; 1141 | 1142 | fail: 1143 | if (item != NULL) 1144 | { 1145 | cJSON_Delete(item); 1146 | } 1147 | 1148 | if (value != NULL) 1149 | { 1150 | error local_error; 1151 | local_error.json = (const unsigned char*)value; 1152 | local_error.position = 0; 1153 | 1154 | if (buffer.offset < buffer.length) 1155 | { 1156 | local_error.position = buffer.offset; 1157 | } 1158 | else if (buffer.length > 0) 1159 | { 1160 | local_error.position = buffer.length - 1; 1161 | } 1162 | 1163 | if (return_parse_end != NULL) 1164 | { 1165 | *return_parse_end = (const char*)local_error.json + local_error.position; 1166 | } 1167 | 1168 | global_error = local_error; 1169 | } 1170 | 1171 | return NULL; 1172 | } 1173 | 1174 | /* Default options for cJSON_Parse */ 1175 | CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) 1176 | { 1177 | return cJSON_ParseWithOpts(value, 0, 0); 1178 | } 1179 | 1180 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) 1181 | { 1182 | return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); 1183 | } 1184 | 1185 | #define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) 1186 | 1187 | static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) 1188 | { 1189 | static const size_t default_buffer_size = 256; 1190 | printbuffer buffer[1]; 1191 | unsigned char *printed = NULL; 1192 | 1193 | memset(buffer, 0, sizeof(buffer)); 1194 | 1195 | /* create buffer */ 1196 | buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); 1197 | buffer->length = default_buffer_size; 1198 | buffer->format = format; 1199 | buffer->hooks = *hooks; 1200 | if (buffer->buffer == NULL) 1201 | { 1202 | goto fail; 1203 | } 1204 | 1205 | /* print the value */ 1206 | if (!print_value(item, buffer)) 1207 | { 1208 | goto fail; 1209 | } 1210 | update_offset(buffer); 1211 | 1212 | /* check if reallocate is available */ 1213 | if (hooks->reallocate != NULL) 1214 | { 1215 | printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); 1216 | if (printed == NULL) { 1217 | goto fail; 1218 | } 1219 | buffer->buffer = NULL; 1220 | } 1221 | else /* otherwise copy the JSON over to a new buffer */ 1222 | { 1223 | printed = (unsigned char*) hooks->allocate(buffer->offset + 1); 1224 | if (printed == NULL) 1225 | { 1226 | goto fail; 1227 | } 1228 | memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); 1229 | printed[buffer->offset] = '\0'; /* just to be sure */ 1230 | 1231 | /* free the buffer */ 1232 | hooks->deallocate(buffer->buffer); 1233 | } 1234 | 1235 | return printed; 1236 | 1237 | fail: 1238 | if (buffer->buffer != NULL) 1239 | { 1240 | hooks->deallocate(buffer->buffer); 1241 | } 1242 | 1243 | if (printed != NULL) 1244 | { 1245 | hooks->deallocate(printed); 1246 | } 1247 | 1248 | return NULL; 1249 | } 1250 | 1251 | /* Render a cJSON item/entity/structure to text. */ 1252 | CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) 1253 | { 1254 | return (char*)print(item, true, &global_hooks); 1255 | } 1256 | 1257 | CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) 1258 | { 1259 | return (char*)print(item, false, &global_hooks); 1260 | } 1261 | 1262 | CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) 1263 | { 1264 | printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; 1265 | 1266 | if (prebuffer < 0) 1267 | { 1268 | return NULL; 1269 | } 1270 | 1271 | p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); 1272 | if (!p.buffer) 1273 | { 1274 | return NULL; 1275 | } 1276 | 1277 | p.length = (size_t)prebuffer; 1278 | p.offset = 0; 1279 | p.noalloc = false; 1280 | p.format = fmt; 1281 | p.hooks = global_hooks; 1282 | 1283 | if (!print_value(item, &p)) 1284 | { 1285 | global_hooks.deallocate(p.buffer); 1286 | return NULL; 1287 | } 1288 | 1289 | return (char*)p.buffer; 1290 | } 1291 | 1292 | CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) 1293 | { 1294 | printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; 1295 | 1296 | if ((length < 0) || (buffer == NULL)) 1297 | { 1298 | return false; 1299 | } 1300 | 1301 | p.buffer = (unsigned char*)buffer; 1302 | p.length = (size_t)length; 1303 | p.offset = 0; 1304 | p.noalloc = true; 1305 | p.format = format; 1306 | p.hooks = global_hooks; 1307 | 1308 | return print_value(item, &p); 1309 | } 1310 | 1311 | /* Parser core - when encountering text, process appropriately. */ 1312 | static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) 1313 | { 1314 | if ((input_buffer == NULL) || (input_buffer->content == NULL)) 1315 | { 1316 | return false; /* no input */ 1317 | } 1318 | 1319 | /* parse the different types of values */ 1320 | /* null */ 1321 | if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) 1322 | { 1323 | item->type = cJSON_NULL; 1324 | input_buffer->offset += 4; 1325 | return true; 1326 | } 1327 | /* false */ 1328 | if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) 1329 | { 1330 | item->type = cJSON_False; 1331 | input_buffer->offset += 5; 1332 | return true; 1333 | } 1334 | /* true */ 1335 | if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) 1336 | { 1337 | item->type = cJSON_True; 1338 | item->valueint = 1; 1339 | input_buffer->offset += 4; 1340 | return true; 1341 | } 1342 | /* string */ 1343 | if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) 1344 | { 1345 | return parse_string(item, input_buffer); 1346 | } 1347 | /* number */ 1348 | if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) 1349 | { 1350 | return parse_number(item, input_buffer); 1351 | } 1352 | /* array */ 1353 | if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) 1354 | { 1355 | return parse_array(item, input_buffer); 1356 | } 1357 | /* object */ 1358 | if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) 1359 | { 1360 | return parse_object(item, input_buffer); 1361 | } 1362 | 1363 | return false; 1364 | } 1365 | 1366 | /* Render a value to text. */ 1367 | static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) 1368 | { 1369 | unsigned char *output = NULL; 1370 | 1371 | if ((item == NULL) || (output_buffer == NULL)) 1372 | { 1373 | return false; 1374 | } 1375 | 1376 | switch ((item->type) & 0xFF) 1377 | { 1378 | case cJSON_NULL: 1379 | output = ensure(output_buffer, 5); 1380 | if (output == NULL) 1381 | { 1382 | return false; 1383 | } 1384 | strcpy((char*)output, "null"); 1385 | return true; 1386 | 1387 | case cJSON_False: 1388 | output = ensure(output_buffer, 6); 1389 | if (output == NULL) 1390 | { 1391 | return false; 1392 | } 1393 | strcpy((char*)output, "false"); 1394 | return true; 1395 | 1396 | case cJSON_True: 1397 | output = ensure(output_buffer, 5); 1398 | if (output == NULL) 1399 | { 1400 | return false; 1401 | } 1402 | strcpy((char*)output, "true"); 1403 | return true; 1404 | 1405 | case cJSON_Number: 1406 | return print_number(item, output_buffer); 1407 | 1408 | case cJSON_Raw: 1409 | { 1410 | size_t raw_length = 0; 1411 | if (item->valuestring == NULL) 1412 | { 1413 | return false; 1414 | } 1415 | 1416 | raw_length = strlen(item->valuestring) + sizeof(""); 1417 | output = ensure(output_buffer, raw_length); 1418 | if (output == NULL) 1419 | { 1420 | return false; 1421 | } 1422 | memcpy(output, item->valuestring, raw_length); 1423 | return true; 1424 | } 1425 | 1426 | case cJSON_String: 1427 | return print_string(item, output_buffer); 1428 | 1429 | case cJSON_Array: 1430 | return print_array(item, output_buffer); 1431 | 1432 | case cJSON_Object: 1433 | return print_object(item, output_buffer); 1434 | 1435 | default: 1436 | return false; 1437 | } 1438 | } 1439 | 1440 | /* Build an array from input text. */ 1441 | static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) 1442 | { 1443 | cJSON *head = NULL; /* head of the linked list */ 1444 | cJSON *current_item = NULL; 1445 | 1446 | if (input_buffer->depth >= CJSON_NESTING_LIMIT) 1447 | { 1448 | return false; /* to deeply nested */ 1449 | } 1450 | input_buffer->depth++; 1451 | 1452 | if (buffer_at_offset(input_buffer)[0] != '[') 1453 | { 1454 | /* not an array */ 1455 | goto fail; 1456 | } 1457 | 1458 | input_buffer->offset++; 1459 | buffer_skip_whitespace(input_buffer); 1460 | if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) 1461 | { 1462 | /* empty array */ 1463 | goto success; 1464 | } 1465 | 1466 | /* check if we skipped to the end of the buffer */ 1467 | if (cannot_access_at_index(input_buffer, 0)) 1468 | { 1469 | input_buffer->offset--; 1470 | goto fail; 1471 | } 1472 | 1473 | /* step back to character in front of the first element */ 1474 | input_buffer->offset--; 1475 | /* loop through the comma separated array elements */ 1476 | do 1477 | { 1478 | /* allocate next item */ 1479 | cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); 1480 | if (new_item == NULL) 1481 | { 1482 | goto fail; /* allocation failure */ 1483 | } 1484 | 1485 | /* attach next item to list */ 1486 | if (head == NULL) 1487 | { 1488 | /* start the linked list */ 1489 | current_item = head = new_item; 1490 | } 1491 | else 1492 | { 1493 | /* add to the end and advance */ 1494 | current_item->next = new_item; 1495 | new_item->prev = current_item; 1496 | current_item = new_item; 1497 | } 1498 | 1499 | /* parse next value */ 1500 | input_buffer->offset++; 1501 | buffer_skip_whitespace(input_buffer); 1502 | if (!parse_value(current_item, input_buffer)) 1503 | { 1504 | goto fail; /* failed to parse value */ 1505 | } 1506 | buffer_skip_whitespace(input_buffer); 1507 | } 1508 | while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); 1509 | 1510 | if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') 1511 | { 1512 | goto fail; /* expected end of array */ 1513 | } 1514 | 1515 | success: 1516 | input_buffer->depth--; 1517 | 1518 | if (head != NULL) { 1519 | head->prev = current_item; 1520 | } 1521 | 1522 | item->type = cJSON_Array; 1523 | item->child = head; 1524 | 1525 | input_buffer->offset++; 1526 | 1527 | return true; 1528 | 1529 | fail: 1530 | if (head != NULL) 1531 | { 1532 | cJSON_Delete(head); 1533 | } 1534 | 1535 | return false; 1536 | } 1537 | 1538 | /* Render an array to text */ 1539 | static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) 1540 | { 1541 | unsigned char *output_pointer = NULL; 1542 | size_t length = 0; 1543 | cJSON *current_element = item->child; 1544 | 1545 | if (output_buffer == NULL) 1546 | { 1547 | return false; 1548 | } 1549 | 1550 | /* Compose the output array. */ 1551 | /* opening square bracket */ 1552 | output_pointer = ensure(output_buffer, 1); 1553 | if (output_pointer == NULL) 1554 | { 1555 | return false; 1556 | } 1557 | 1558 | *output_pointer = '['; 1559 | output_buffer->offset++; 1560 | output_buffer->depth++; 1561 | 1562 | while (current_element != NULL) 1563 | { 1564 | if (!print_value(current_element, output_buffer)) 1565 | { 1566 | return false; 1567 | } 1568 | update_offset(output_buffer); 1569 | if (current_element->next) 1570 | { 1571 | length = (size_t) (output_buffer->format ? 2 : 1); 1572 | output_pointer = ensure(output_buffer, length + 1); 1573 | if (output_pointer == NULL) 1574 | { 1575 | return false; 1576 | } 1577 | *output_pointer++ = ','; 1578 | if(output_buffer->format) 1579 | { 1580 | *output_pointer++ = ' '; 1581 | } 1582 | *output_pointer = '\0'; 1583 | output_buffer->offset += length; 1584 | } 1585 | current_element = current_element->next; 1586 | } 1587 | 1588 | output_pointer = ensure(output_buffer, 2); 1589 | if (output_pointer == NULL) 1590 | { 1591 | return false; 1592 | } 1593 | *output_pointer++ = ']'; 1594 | *output_pointer = '\0'; 1595 | output_buffer->depth--; 1596 | 1597 | return true; 1598 | } 1599 | 1600 | /* Build an object from the text. */ 1601 | static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) 1602 | { 1603 | cJSON *head = NULL; /* linked list head */ 1604 | cJSON *current_item = NULL; 1605 | 1606 | if (input_buffer->depth >= CJSON_NESTING_LIMIT) 1607 | { 1608 | return false; /* to deeply nested */ 1609 | } 1610 | input_buffer->depth++; 1611 | 1612 | if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) 1613 | { 1614 | goto fail; /* not an object */ 1615 | } 1616 | 1617 | input_buffer->offset++; 1618 | buffer_skip_whitespace(input_buffer); 1619 | if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) 1620 | { 1621 | goto success; /* empty object */ 1622 | } 1623 | 1624 | /* check if we skipped to the end of the buffer */ 1625 | if (cannot_access_at_index(input_buffer, 0)) 1626 | { 1627 | input_buffer->offset--; 1628 | goto fail; 1629 | } 1630 | 1631 | /* step back to character in front of the first element */ 1632 | input_buffer->offset--; 1633 | /* loop through the comma separated array elements */ 1634 | do 1635 | { 1636 | /* allocate next item */ 1637 | cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); 1638 | if (new_item == NULL) 1639 | { 1640 | goto fail; /* allocation failure */ 1641 | } 1642 | 1643 | /* attach next item to list */ 1644 | if (head == NULL) 1645 | { 1646 | /* start the linked list */ 1647 | current_item = head = new_item; 1648 | } 1649 | else 1650 | { 1651 | /* add to the end and advance */ 1652 | current_item->next = new_item; 1653 | new_item->prev = current_item; 1654 | current_item = new_item; 1655 | } 1656 | 1657 | /* parse the name of the child */ 1658 | input_buffer->offset++; 1659 | buffer_skip_whitespace(input_buffer); 1660 | if (!parse_string(current_item, input_buffer)) 1661 | { 1662 | goto fail; /* failed to parse name */ 1663 | } 1664 | buffer_skip_whitespace(input_buffer); 1665 | 1666 | /* swap valuestring and string, because we parsed the name */ 1667 | current_item->string = current_item->valuestring; 1668 | current_item->valuestring = NULL; 1669 | 1670 | if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) 1671 | { 1672 | goto fail; /* invalid object */ 1673 | } 1674 | 1675 | /* parse the value */ 1676 | input_buffer->offset++; 1677 | buffer_skip_whitespace(input_buffer); 1678 | if (!parse_value(current_item, input_buffer)) 1679 | { 1680 | goto fail; /* failed to parse value */ 1681 | } 1682 | buffer_skip_whitespace(input_buffer); 1683 | } 1684 | while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); 1685 | 1686 | if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) 1687 | { 1688 | goto fail; /* expected end of object */ 1689 | } 1690 | 1691 | success: 1692 | input_buffer->depth--; 1693 | 1694 | if (head != NULL) { 1695 | head->prev = current_item; 1696 | } 1697 | 1698 | item->type = cJSON_Object; 1699 | item->child = head; 1700 | 1701 | input_buffer->offset++; 1702 | return true; 1703 | 1704 | fail: 1705 | if (head != NULL) 1706 | { 1707 | cJSON_Delete(head); 1708 | } 1709 | 1710 | return false; 1711 | } 1712 | 1713 | /* Render an object to text. */ 1714 | static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) 1715 | { 1716 | unsigned char *output_pointer = NULL; 1717 | size_t length = 0; 1718 | cJSON *current_item = item->child; 1719 | 1720 | if (output_buffer == NULL) 1721 | { 1722 | return false; 1723 | } 1724 | 1725 | /* Compose the output: */ 1726 | length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ 1727 | output_pointer = ensure(output_buffer, length + 1); 1728 | if (output_pointer == NULL) 1729 | { 1730 | return false; 1731 | } 1732 | 1733 | *output_pointer++ = '{'; 1734 | output_buffer->depth++; 1735 | if (output_buffer->format) 1736 | { 1737 | *output_pointer++ = '\n'; 1738 | } 1739 | output_buffer->offset += length; 1740 | 1741 | while (current_item) 1742 | { 1743 | if (output_buffer->format) 1744 | { 1745 | size_t i; 1746 | output_pointer = ensure(output_buffer, output_buffer->depth); 1747 | if (output_pointer == NULL) 1748 | { 1749 | return false; 1750 | } 1751 | for (i = 0; i < output_buffer->depth; i++) 1752 | { 1753 | *output_pointer++ = '\t'; 1754 | } 1755 | output_buffer->offset += output_buffer->depth; 1756 | } 1757 | 1758 | /* print key */ 1759 | if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) 1760 | { 1761 | return false; 1762 | } 1763 | update_offset(output_buffer); 1764 | 1765 | length = (size_t) (output_buffer->format ? 2 : 1); 1766 | output_pointer = ensure(output_buffer, length); 1767 | if (output_pointer == NULL) 1768 | { 1769 | return false; 1770 | } 1771 | *output_pointer++ = ':'; 1772 | if (output_buffer->format) 1773 | { 1774 | *output_pointer++ = '\t'; 1775 | } 1776 | output_buffer->offset += length; 1777 | 1778 | /* print value */ 1779 | if (!print_value(current_item, output_buffer)) 1780 | { 1781 | return false; 1782 | } 1783 | update_offset(output_buffer); 1784 | 1785 | /* print comma if not last */ 1786 | length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); 1787 | output_pointer = ensure(output_buffer, length + 1); 1788 | if (output_pointer == NULL) 1789 | { 1790 | return false; 1791 | } 1792 | if (current_item->next) 1793 | { 1794 | *output_pointer++ = ','; 1795 | } 1796 | 1797 | if (output_buffer->format) 1798 | { 1799 | *output_pointer++ = '\n'; 1800 | } 1801 | *output_pointer = '\0'; 1802 | output_buffer->offset += length; 1803 | 1804 | current_item = current_item->next; 1805 | } 1806 | 1807 | output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); 1808 | if (output_pointer == NULL) 1809 | { 1810 | return false; 1811 | } 1812 | if (output_buffer->format) 1813 | { 1814 | size_t i; 1815 | for (i = 0; i < (output_buffer->depth - 1); i++) 1816 | { 1817 | *output_pointer++ = '\t'; 1818 | } 1819 | } 1820 | *output_pointer++ = '}'; 1821 | *output_pointer = '\0'; 1822 | output_buffer->depth--; 1823 | 1824 | return true; 1825 | } 1826 | 1827 | /* Get Array size/item / object item. */ 1828 | CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) 1829 | { 1830 | cJSON *child = NULL; 1831 | size_t size = 0; 1832 | 1833 | if (array == NULL) 1834 | { 1835 | return 0; 1836 | } 1837 | 1838 | child = array->child; 1839 | 1840 | while(child != NULL) 1841 | { 1842 | size++; 1843 | child = child->next; 1844 | } 1845 | 1846 | /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ 1847 | 1848 | return (int)size; 1849 | } 1850 | 1851 | static cJSON* get_array_item(const cJSON *array, size_t index) 1852 | { 1853 | cJSON *current_child = NULL; 1854 | 1855 | if (array == NULL) 1856 | { 1857 | return NULL; 1858 | } 1859 | 1860 | current_child = array->child; 1861 | while ((current_child != NULL) && (index > 0)) 1862 | { 1863 | index--; 1864 | current_child = current_child->next; 1865 | } 1866 | 1867 | return current_child; 1868 | } 1869 | 1870 | CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) 1871 | { 1872 | if (index < 0) 1873 | { 1874 | return NULL; 1875 | } 1876 | 1877 | return get_array_item(array, (size_t)index); 1878 | } 1879 | 1880 | static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) 1881 | { 1882 | cJSON *current_element = NULL; 1883 | 1884 | if ((object == NULL) || (name == NULL)) 1885 | { 1886 | return NULL; 1887 | } 1888 | 1889 | current_element = object->child; 1890 | if (case_sensitive) 1891 | { 1892 | while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) 1893 | { 1894 | current_element = current_element->next; 1895 | } 1896 | } 1897 | else 1898 | { 1899 | while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) 1900 | { 1901 | current_element = current_element->next; 1902 | } 1903 | } 1904 | 1905 | if ((current_element == NULL) || (current_element->string == NULL)) { 1906 | return NULL; 1907 | } 1908 | 1909 | return current_element; 1910 | } 1911 | 1912 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) 1913 | { 1914 | return get_object_item(object, string, false); 1915 | } 1916 | 1917 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) 1918 | { 1919 | return get_object_item(object, string, true); 1920 | } 1921 | 1922 | CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) 1923 | { 1924 | return cJSON_GetObjectItem(object, string) ? 1 : 0; 1925 | } 1926 | 1927 | /* Utility for array list handling. */ 1928 | static void suffix_object(cJSON *prev, cJSON *item) 1929 | { 1930 | prev->next = item; 1931 | item->prev = prev; 1932 | } 1933 | 1934 | /* Utility for handling references. */ 1935 | static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) 1936 | { 1937 | cJSON *reference = NULL; 1938 | if (item == NULL) 1939 | { 1940 | return NULL; 1941 | } 1942 | 1943 | reference = cJSON_New_Item(hooks); 1944 | if (reference == NULL) 1945 | { 1946 | return NULL; 1947 | } 1948 | 1949 | memcpy(reference, item, sizeof(cJSON)); 1950 | reference->string = NULL; 1951 | reference->type |= cJSON_IsReference; 1952 | reference->next = reference->prev = NULL; 1953 | return reference; 1954 | } 1955 | 1956 | static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) 1957 | { 1958 | cJSON *child = NULL; 1959 | 1960 | if ((item == NULL) || (array == NULL) || (array == item)) 1961 | { 1962 | return false; 1963 | } 1964 | 1965 | child = array->child; 1966 | /* 1967 | * To find the last item in array quickly, we use prev in array 1968 | */ 1969 | if (child == NULL) 1970 | { 1971 | /* list is empty, start new one */ 1972 | array->child = item; 1973 | item->prev = item; 1974 | item->next = NULL; 1975 | } 1976 | else 1977 | { 1978 | /* append to the end */ 1979 | if (child->prev) 1980 | { 1981 | suffix_object(child->prev, item); 1982 | array->child->prev = item; 1983 | } 1984 | } 1985 | 1986 | return true; 1987 | } 1988 | 1989 | /* Add item to array/object. */ 1990 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) 1991 | { 1992 | return add_item_to_array(array, item); 1993 | } 1994 | 1995 | #if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) 1996 | #pragma GCC diagnostic push 1997 | #endif 1998 | #ifdef __GNUC__ 1999 | #pragma GCC diagnostic ignored "-Wcast-qual" 2000 | #endif 2001 | /* helper function to cast away const */ 2002 | static void* cast_away_const(const void* string) 2003 | { 2004 | return (void*)string; 2005 | } 2006 | #if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) 2007 | #pragma GCC diagnostic pop 2008 | #endif 2009 | 2010 | 2011 | static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) 2012 | { 2013 | char *new_key = NULL; 2014 | int new_type = cJSON_Invalid; 2015 | 2016 | if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) 2017 | { 2018 | return false; 2019 | } 2020 | 2021 | if (constant_key) 2022 | { 2023 | new_key = (char*)cast_away_const(string); 2024 | new_type = item->type | cJSON_StringIsConst; 2025 | } 2026 | else 2027 | { 2028 | new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); 2029 | if (new_key == NULL) 2030 | { 2031 | return false; 2032 | } 2033 | 2034 | new_type = item->type & ~cJSON_StringIsConst; 2035 | } 2036 | 2037 | if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) 2038 | { 2039 | hooks->deallocate(item->string); 2040 | } 2041 | 2042 | item->string = new_key; 2043 | item->type = new_type; 2044 | 2045 | return add_item_to_array(object, item); 2046 | } 2047 | 2048 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) 2049 | { 2050 | return add_item_to_object(object, string, item, &global_hooks, false); 2051 | } 2052 | 2053 | /* Add an item to an object with constant string as key */ 2054 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) 2055 | { 2056 | return add_item_to_object(object, string, item, &global_hooks, true); 2057 | } 2058 | 2059 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) 2060 | { 2061 | if (array == NULL) 2062 | { 2063 | return false; 2064 | } 2065 | 2066 | return add_item_to_array(array, create_reference(item, &global_hooks)); 2067 | } 2068 | 2069 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) 2070 | { 2071 | if ((object == NULL) || (string == NULL)) 2072 | { 2073 | return false; 2074 | } 2075 | 2076 | return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); 2077 | } 2078 | 2079 | CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) 2080 | { 2081 | cJSON *null = cJSON_CreateNull(); 2082 | if (add_item_to_object(object, name, null, &global_hooks, false)) 2083 | { 2084 | return null; 2085 | } 2086 | 2087 | cJSON_Delete(null); 2088 | return NULL; 2089 | } 2090 | 2091 | CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) 2092 | { 2093 | cJSON *true_item = cJSON_CreateTrue(); 2094 | if (add_item_to_object(object, name, true_item, &global_hooks, false)) 2095 | { 2096 | return true_item; 2097 | } 2098 | 2099 | cJSON_Delete(true_item); 2100 | return NULL; 2101 | } 2102 | 2103 | CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) 2104 | { 2105 | cJSON *false_item = cJSON_CreateFalse(); 2106 | if (add_item_to_object(object, name, false_item, &global_hooks, false)) 2107 | { 2108 | return false_item; 2109 | } 2110 | 2111 | cJSON_Delete(false_item); 2112 | return NULL; 2113 | } 2114 | 2115 | CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) 2116 | { 2117 | cJSON *bool_item = cJSON_CreateBool(boolean); 2118 | if (add_item_to_object(object, name, bool_item, &global_hooks, false)) 2119 | { 2120 | return bool_item; 2121 | } 2122 | 2123 | cJSON_Delete(bool_item); 2124 | return NULL; 2125 | } 2126 | 2127 | CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) 2128 | { 2129 | cJSON *number_item = cJSON_CreateNumber(number); 2130 | if (add_item_to_object(object, name, number_item, &global_hooks, false)) 2131 | { 2132 | return number_item; 2133 | } 2134 | 2135 | cJSON_Delete(number_item); 2136 | return NULL; 2137 | } 2138 | 2139 | CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) 2140 | { 2141 | cJSON *string_item = cJSON_CreateString(string); 2142 | if (add_item_to_object(object, name, string_item, &global_hooks, false)) 2143 | { 2144 | return string_item; 2145 | } 2146 | 2147 | cJSON_Delete(string_item); 2148 | return NULL; 2149 | } 2150 | 2151 | CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) 2152 | { 2153 | cJSON *raw_item = cJSON_CreateRaw(raw); 2154 | if (add_item_to_object(object, name, raw_item, &global_hooks, false)) 2155 | { 2156 | return raw_item; 2157 | } 2158 | 2159 | cJSON_Delete(raw_item); 2160 | return NULL; 2161 | } 2162 | 2163 | CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) 2164 | { 2165 | cJSON *object_item = cJSON_CreateObject(); 2166 | if (add_item_to_object(object, name, object_item, &global_hooks, false)) 2167 | { 2168 | return object_item; 2169 | } 2170 | 2171 | cJSON_Delete(object_item); 2172 | return NULL; 2173 | } 2174 | 2175 | CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) 2176 | { 2177 | cJSON *array = cJSON_CreateArray(); 2178 | if (add_item_to_object(object, name, array, &global_hooks, false)) 2179 | { 2180 | return array; 2181 | } 2182 | 2183 | cJSON_Delete(array); 2184 | return NULL; 2185 | } 2186 | 2187 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) 2188 | { 2189 | if ((parent == NULL) || (item == NULL)) 2190 | { 2191 | return NULL; 2192 | } 2193 | 2194 | if (item != parent->child) 2195 | { 2196 | /* not the first element */ 2197 | item->prev->next = item->next; 2198 | } 2199 | if (item->next != NULL) 2200 | { 2201 | /* not the last element */ 2202 | item->next->prev = item->prev; 2203 | } 2204 | 2205 | if (item == parent->child) 2206 | { 2207 | /* first element */ 2208 | parent->child = item->next; 2209 | } 2210 | else if (item->next == NULL) 2211 | { 2212 | /* last element */ 2213 | parent->child->prev = item->prev; 2214 | } 2215 | 2216 | /* make sure the detached item doesn't point anywhere anymore */ 2217 | item->prev = NULL; 2218 | item->next = NULL; 2219 | 2220 | return item; 2221 | } 2222 | 2223 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) 2224 | { 2225 | if (which < 0) 2226 | { 2227 | return NULL; 2228 | } 2229 | 2230 | return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); 2231 | } 2232 | 2233 | CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) 2234 | { 2235 | cJSON_Delete(cJSON_DetachItemFromArray(array, which)); 2236 | } 2237 | 2238 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) 2239 | { 2240 | cJSON *to_detach = cJSON_GetObjectItem(object, string); 2241 | 2242 | return cJSON_DetachItemViaPointer(object, to_detach); 2243 | } 2244 | 2245 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) 2246 | { 2247 | cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); 2248 | 2249 | return cJSON_DetachItemViaPointer(object, to_detach); 2250 | } 2251 | 2252 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) 2253 | { 2254 | cJSON_Delete(cJSON_DetachItemFromObject(object, string)); 2255 | } 2256 | 2257 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) 2258 | { 2259 | cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); 2260 | } 2261 | 2262 | /* Replace array/object items with new ones. */ 2263 | CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) 2264 | { 2265 | cJSON *after_inserted = NULL; 2266 | 2267 | if (which < 0) 2268 | { 2269 | return false; 2270 | } 2271 | 2272 | after_inserted = get_array_item(array, (size_t)which); 2273 | if (after_inserted == NULL) 2274 | { 2275 | return add_item_to_array(array, newitem); 2276 | } 2277 | 2278 | newitem->next = after_inserted; 2279 | newitem->prev = after_inserted->prev; 2280 | after_inserted->prev = newitem; 2281 | if (after_inserted == array->child) 2282 | { 2283 | array->child = newitem; 2284 | } 2285 | else 2286 | { 2287 | newitem->prev->next = newitem; 2288 | } 2289 | return true; 2290 | } 2291 | 2292 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) 2293 | { 2294 | if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL)) 2295 | { 2296 | return false; 2297 | } 2298 | 2299 | if (replacement == item) 2300 | { 2301 | return true; 2302 | } 2303 | 2304 | replacement->next = item->next; 2305 | replacement->prev = item->prev; 2306 | 2307 | if (replacement->next != NULL) 2308 | { 2309 | replacement->next->prev = replacement; 2310 | } 2311 | if (parent->child == item) 2312 | { 2313 | if (parent->child->prev == parent->child) 2314 | { 2315 | replacement->prev = replacement; 2316 | } 2317 | parent->child = replacement; 2318 | } 2319 | else 2320 | { /* 2321 | * To find the last item in array quickly, we use prev in array. 2322 | * We can't modify the last item's next pointer where this item was the parent's child 2323 | */ 2324 | if (replacement->prev != NULL) 2325 | { 2326 | replacement->prev->next = replacement; 2327 | } 2328 | if (replacement->next == NULL) 2329 | { 2330 | parent->child->prev = replacement; 2331 | } 2332 | } 2333 | 2334 | item->next = NULL; 2335 | item->prev = NULL; 2336 | cJSON_Delete(item); 2337 | 2338 | return true; 2339 | } 2340 | 2341 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) 2342 | { 2343 | if (which < 0) 2344 | { 2345 | return false; 2346 | } 2347 | 2348 | return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); 2349 | } 2350 | 2351 | static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) 2352 | { 2353 | if ((replacement == NULL) || (string == NULL)) 2354 | { 2355 | return false; 2356 | } 2357 | 2358 | /* replace the name in the replacement */ 2359 | if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) 2360 | { 2361 | cJSON_free(replacement->string); 2362 | } 2363 | replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); 2364 | if (replacement->string == NULL) 2365 | { 2366 | return false; 2367 | } 2368 | 2369 | replacement->type &= ~cJSON_StringIsConst; 2370 | 2371 | return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); 2372 | } 2373 | 2374 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) 2375 | { 2376 | return replace_item_in_object(object, string, newitem, false); 2377 | } 2378 | 2379 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) 2380 | { 2381 | return replace_item_in_object(object, string, newitem, true); 2382 | } 2383 | 2384 | /* Create basic types: */ 2385 | CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) 2386 | { 2387 | cJSON *item = cJSON_New_Item(&global_hooks); 2388 | if(item) 2389 | { 2390 | item->type = cJSON_NULL; 2391 | } 2392 | 2393 | return item; 2394 | } 2395 | 2396 | CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) 2397 | { 2398 | cJSON *item = cJSON_New_Item(&global_hooks); 2399 | if(item) 2400 | { 2401 | item->type = cJSON_True; 2402 | } 2403 | 2404 | return item; 2405 | } 2406 | 2407 | CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) 2408 | { 2409 | cJSON *item = cJSON_New_Item(&global_hooks); 2410 | if(item) 2411 | { 2412 | item->type = cJSON_False; 2413 | } 2414 | 2415 | return item; 2416 | } 2417 | 2418 | CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) 2419 | { 2420 | cJSON *item = cJSON_New_Item(&global_hooks); 2421 | if(item) 2422 | { 2423 | item->type = boolean ? cJSON_True : cJSON_False; 2424 | } 2425 | 2426 | return item; 2427 | } 2428 | 2429 | CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) 2430 | { 2431 | cJSON *item = cJSON_New_Item(&global_hooks); 2432 | if(item) 2433 | { 2434 | item->type = cJSON_Number; 2435 | item->valuedouble = num; 2436 | 2437 | /* use saturation in case of overflow */ 2438 | if (num >= INT_MAX) 2439 | { 2440 | item->valueint = INT_MAX; 2441 | } 2442 | else if (num <= (double)INT_MIN) 2443 | { 2444 | item->valueint = INT_MIN; 2445 | } 2446 | else 2447 | { 2448 | item->valueint = (int)num; 2449 | } 2450 | } 2451 | 2452 | return item; 2453 | } 2454 | 2455 | CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) 2456 | { 2457 | cJSON *item = cJSON_New_Item(&global_hooks); 2458 | if(item) 2459 | { 2460 | item->type = cJSON_String; 2461 | item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); 2462 | if(!item->valuestring) 2463 | { 2464 | cJSON_Delete(item); 2465 | return NULL; 2466 | } 2467 | } 2468 | 2469 | return item; 2470 | } 2471 | 2472 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) 2473 | { 2474 | cJSON *item = cJSON_New_Item(&global_hooks); 2475 | if (item != NULL) 2476 | { 2477 | item->type = cJSON_String | cJSON_IsReference; 2478 | item->valuestring = (char*)cast_away_const(string); 2479 | } 2480 | 2481 | return item; 2482 | } 2483 | 2484 | CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) 2485 | { 2486 | cJSON *item = cJSON_New_Item(&global_hooks); 2487 | if (item != NULL) { 2488 | item->type = cJSON_Object | cJSON_IsReference; 2489 | item->child = (cJSON*)cast_away_const(child); 2490 | } 2491 | 2492 | return item; 2493 | } 2494 | 2495 | CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { 2496 | cJSON *item = cJSON_New_Item(&global_hooks); 2497 | if (item != NULL) { 2498 | item->type = cJSON_Array | cJSON_IsReference; 2499 | item->child = (cJSON*)cast_away_const(child); 2500 | } 2501 | 2502 | return item; 2503 | } 2504 | 2505 | CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) 2506 | { 2507 | cJSON *item = cJSON_New_Item(&global_hooks); 2508 | if(item) 2509 | { 2510 | item->type = cJSON_Raw; 2511 | item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); 2512 | if(!item->valuestring) 2513 | { 2514 | cJSON_Delete(item); 2515 | return NULL; 2516 | } 2517 | } 2518 | 2519 | return item; 2520 | } 2521 | 2522 | CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) 2523 | { 2524 | cJSON *item = cJSON_New_Item(&global_hooks); 2525 | if(item) 2526 | { 2527 | item->type=cJSON_Array; 2528 | } 2529 | 2530 | return item; 2531 | } 2532 | 2533 | CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) 2534 | { 2535 | cJSON *item = cJSON_New_Item(&global_hooks); 2536 | if (item) 2537 | { 2538 | item->type = cJSON_Object; 2539 | } 2540 | 2541 | return item; 2542 | } 2543 | 2544 | /* Create Arrays: */ 2545 | CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) 2546 | { 2547 | size_t i = 0; 2548 | cJSON *n = NULL; 2549 | cJSON *p = NULL; 2550 | cJSON *a = NULL; 2551 | 2552 | if ((count < 0) || (numbers == NULL)) 2553 | { 2554 | return NULL; 2555 | } 2556 | 2557 | a = cJSON_CreateArray(); 2558 | 2559 | for(i = 0; a && (i < (size_t)count); i++) 2560 | { 2561 | n = cJSON_CreateNumber(numbers[i]); 2562 | if (!n) 2563 | { 2564 | cJSON_Delete(a); 2565 | return NULL; 2566 | } 2567 | if(!i) 2568 | { 2569 | a->child = n; 2570 | } 2571 | else 2572 | { 2573 | suffix_object(p, n); 2574 | } 2575 | p = n; 2576 | } 2577 | 2578 | if (a && a->child) { 2579 | a->child->prev = n; 2580 | } 2581 | 2582 | return a; 2583 | } 2584 | 2585 | CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) 2586 | { 2587 | size_t i = 0; 2588 | cJSON *n = NULL; 2589 | cJSON *p = NULL; 2590 | cJSON *a = NULL; 2591 | 2592 | if ((count < 0) || (numbers == NULL)) 2593 | { 2594 | return NULL; 2595 | } 2596 | 2597 | a = cJSON_CreateArray(); 2598 | 2599 | for(i = 0; a && (i < (size_t)count); i++) 2600 | { 2601 | n = cJSON_CreateNumber((double)numbers[i]); 2602 | if(!n) 2603 | { 2604 | cJSON_Delete(a); 2605 | return NULL; 2606 | } 2607 | if(!i) 2608 | { 2609 | a->child = n; 2610 | } 2611 | else 2612 | { 2613 | suffix_object(p, n); 2614 | } 2615 | p = n; 2616 | } 2617 | 2618 | if (a && a->child) { 2619 | a->child->prev = n; 2620 | } 2621 | 2622 | return a; 2623 | } 2624 | 2625 | CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) 2626 | { 2627 | size_t i = 0; 2628 | cJSON *n = NULL; 2629 | cJSON *p = NULL; 2630 | cJSON *a = NULL; 2631 | 2632 | if ((count < 0) || (numbers == NULL)) 2633 | { 2634 | return NULL; 2635 | } 2636 | 2637 | a = cJSON_CreateArray(); 2638 | 2639 | for(i = 0; a && (i < (size_t)count); i++) 2640 | { 2641 | n = cJSON_CreateNumber(numbers[i]); 2642 | if(!n) 2643 | { 2644 | cJSON_Delete(a); 2645 | return NULL; 2646 | } 2647 | if(!i) 2648 | { 2649 | a->child = n; 2650 | } 2651 | else 2652 | { 2653 | suffix_object(p, n); 2654 | } 2655 | p = n; 2656 | } 2657 | 2658 | if (a && a->child) { 2659 | a->child->prev = n; 2660 | } 2661 | 2662 | return a; 2663 | } 2664 | 2665 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) 2666 | { 2667 | size_t i = 0; 2668 | cJSON *n = NULL; 2669 | cJSON *p = NULL; 2670 | cJSON *a = NULL; 2671 | 2672 | if ((count < 0) || (strings == NULL)) 2673 | { 2674 | return NULL; 2675 | } 2676 | 2677 | a = cJSON_CreateArray(); 2678 | 2679 | for (i = 0; a && (i < (size_t)count); i++) 2680 | { 2681 | n = cJSON_CreateString(strings[i]); 2682 | if(!n) 2683 | { 2684 | cJSON_Delete(a); 2685 | return NULL; 2686 | } 2687 | if(!i) 2688 | { 2689 | a->child = n; 2690 | } 2691 | else 2692 | { 2693 | suffix_object(p,n); 2694 | } 2695 | p = n; 2696 | } 2697 | 2698 | if (a && a->child) { 2699 | a->child->prev = n; 2700 | } 2701 | 2702 | return a; 2703 | } 2704 | 2705 | /* Duplication */ 2706 | CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) 2707 | { 2708 | cJSON *newitem = NULL; 2709 | cJSON *child = NULL; 2710 | cJSON *next = NULL; 2711 | cJSON *newchild = NULL; 2712 | 2713 | /* Bail on bad ptr */ 2714 | if (!item) 2715 | { 2716 | goto fail; 2717 | } 2718 | /* Create new item */ 2719 | newitem = cJSON_New_Item(&global_hooks); 2720 | if (!newitem) 2721 | { 2722 | goto fail; 2723 | } 2724 | /* Copy over all vars */ 2725 | newitem->type = item->type & (~cJSON_IsReference); 2726 | newitem->valueint = item->valueint; 2727 | newitem->valuedouble = item->valuedouble; 2728 | if (item->valuestring) 2729 | { 2730 | newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); 2731 | if (!newitem->valuestring) 2732 | { 2733 | goto fail; 2734 | } 2735 | } 2736 | if (item->string) 2737 | { 2738 | newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); 2739 | if (!newitem->string) 2740 | { 2741 | goto fail; 2742 | } 2743 | } 2744 | /* If non-recursive, then we're done! */ 2745 | if (!recurse) 2746 | { 2747 | return newitem; 2748 | } 2749 | /* Walk the ->next chain for the child. */ 2750 | child = item->child; 2751 | while (child != NULL) 2752 | { 2753 | newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ 2754 | if (!newchild) 2755 | { 2756 | goto fail; 2757 | } 2758 | if (next != NULL) 2759 | { 2760 | /* If newitem->child already set, then crosswire ->prev and ->next and move on */ 2761 | next->next = newchild; 2762 | newchild->prev = next; 2763 | next = newchild; 2764 | } 2765 | else 2766 | { 2767 | /* Set newitem->child and move to it */ 2768 | newitem->child = newchild; 2769 | next = newchild; 2770 | } 2771 | child = child->next; 2772 | } 2773 | if (newitem && newitem->child) 2774 | { 2775 | newitem->child->prev = newchild; 2776 | } 2777 | 2778 | return newitem; 2779 | 2780 | fail: 2781 | if (newitem != NULL) 2782 | { 2783 | cJSON_Delete(newitem); 2784 | } 2785 | 2786 | return NULL; 2787 | } 2788 | 2789 | static void skip_oneline_comment(char **input) 2790 | { 2791 | *input += static_strlen("//"); 2792 | 2793 | for (; (*input)[0] != '\0'; ++(*input)) 2794 | { 2795 | if ((*input)[0] == '\n') { 2796 | *input += static_strlen("\n"); 2797 | return; 2798 | } 2799 | } 2800 | } 2801 | 2802 | static void skip_multiline_comment(char **input) 2803 | { 2804 | *input += static_strlen("/*"); 2805 | 2806 | for (; (*input)[0] != '\0'; ++(*input)) 2807 | { 2808 | if (((*input)[0] == '*') && ((*input)[1] == '/')) 2809 | { 2810 | *input += static_strlen("*/"); 2811 | return; 2812 | } 2813 | } 2814 | } 2815 | 2816 | static void minify_string(char **input, char **output) { 2817 | (*output)[0] = (*input)[0]; 2818 | *input += static_strlen("\""); 2819 | *output += static_strlen("\""); 2820 | 2821 | 2822 | for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { 2823 | (*output)[0] = (*input)[0]; 2824 | 2825 | if ((*input)[0] == '\"') { 2826 | (*output)[0] = '\"'; 2827 | *input += static_strlen("\""); 2828 | *output += static_strlen("\""); 2829 | return; 2830 | } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { 2831 | (*output)[1] = (*input)[1]; 2832 | *input += static_strlen("\""); 2833 | *output += static_strlen("\""); 2834 | } 2835 | } 2836 | } 2837 | 2838 | CJSON_PUBLIC(void) cJSON_Minify(char *json) 2839 | { 2840 | char *into = json; 2841 | 2842 | if (json == NULL) 2843 | { 2844 | return; 2845 | } 2846 | 2847 | while (json[0] != '\0') 2848 | { 2849 | switch (json[0]) 2850 | { 2851 | case ' ': 2852 | case '\t': 2853 | case '\r': 2854 | case '\n': 2855 | json++; 2856 | break; 2857 | 2858 | case '/': 2859 | if (json[1] == '/') 2860 | { 2861 | skip_oneline_comment(&json); 2862 | } 2863 | else if (json[1] == '*') 2864 | { 2865 | skip_multiline_comment(&json); 2866 | } else { 2867 | json++; 2868 | } 2869 | break; 2870 | 2871 | case '\"': 2872 | minify_string(&json, (char**)&into); 2873 | break; 2874 | 2875 | default: 2876 | into[0] = json[0]; 2877 | json++; 2878 | into++; 2879 | } 2880 | } 2881 | 2882 | /* and null-terminate. */ 2883 | *into = '\0'; 2884 | } 2885 | 2886 | CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) 2887 | { 2888 | if (item == NULL) 2889 | { 2890 | return false; 2891 | } 2892 | 2893 | return (item->type & 0xFF) == cJSON_Invalid; 2894 | } 2895 | 2896 | CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) 2897 | { 2898 | if (item == NULL) 2899 | { 2900 | return false; 2901 | } 2902 | 2903 | return (item->type & 0xFF) == cJSON_False; 2904 | } 2905 | 2906 | CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) 2907 | { 2908 | if (item == NULL) 2909 | { 2910 | return false; 2911 | } 2912 | 2913 | return (item->type & 0xff) == cJSON_True; 2914 | } 2915 | 2916 | 2917 | CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) 2918 | { 2919 | if (item == NULL) 2920 | { 2921 | return false; 2922 | } 2923 | 2924 | return (item->type & (cJSON_True | cJSON_False)) != 0; 2925 | } 2926 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) 2927 | { 2928 | if (item == NULL) 2929 | { 2930 | return false; 2931 | } 2932 | 2933 | return (item->type & 0xFF) == cJSON_NULL; 2934 | } 2935 | 2936 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) 2937 | { 2938 | if (item == NULL) 2939 | { 2940 | return false; 2941 | } 2942 | 2943 | return (item->type & 0xFF) == cJSON_Number; 2944 | } 2945 | 2946 | CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) 2947 | { 2948 | if (item == NULL) 2949 | { 2950 | return false; 2951 | } 2952 | 2953 | return (item->type & 0xFF) == cJSON_String; 2954 | } 2955 | 2956 | CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) 2957 | { 2958 | if (item == NULL) 2959 | { 2960 | return false; 2961 | } 2962 | 2963 | return (item->type & 0xFF) == cJSON_Array; 2964 | } 2965 | 2966 | CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) 2967 | { 2968 | if (item == NULL) 2969 | { 2970 | return false; 2971 | } 2972 | 2973 | return (item->type & 0xFF) == cJSON_Object; 2974 | } 2975 | 2976 | CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) 2977 | { 2978 | if (item == NULL) 2979 | { 2980 | return false; 2981 | } 2982 | 2983 | return (item->type & 0xFF) == cJSON_Raw; 2984 | } 2985 | 2986 | CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) 2987 | { 2988 | if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) 2989 | { 2990 | return false; 2991 | } 2992 | 2993 | /* check if type is valid */ 2994 | switch (a->type & 0xFF) 2995 | { 2996 | case cJSON_False: 2997 | case cJSON_True: 2998 | case cJSON_NULL: 2999 | case cJSON_Number: 3000 | case cJSON_String: 3001 | case cJSON_Raw: 3002 | case cJSON_Array: 3003 | case cJSON_Object: 3004 | break; 3005 | 3006 | default: 3007 | return false; 3008 | } 3009 | 3010 | /* identical objects are equal */ 3011 | if (a == b) 3012 | { 3013 | return true; 3014 | } 3015 | 3016 | switch (a->type & 0xFF) 3017 | { 3018 | /* in these cases and equal type is enough */ 3019 | case cJSON_False: 3020 | case cJSON_True: 3021 | case cJSON_NULL: 3022 | return true; 3023 | 3024 | case cJSON_Number: 3025 | if (compare_double(a->valuedouble, b->valuedouble)) 3026 | { 3027 | return true; 3028 | } 3029 | return false; 3030 | 3031 | case cJSON_String: 3032 | case cJSON_Raw: 3033 | if ((a->valuestring == NULL) || (b->valuestring == NULL)) 3034 | { 3035 | return false; 3036 | } 3037 | if (strcmp(a->valuestring, b->valuestring) == 0) 3038 | { 3039 | return true; 3040 | } 3041 | 3042 | return false; 3043 | 3044 | case cJSON_Array: 3045 | { 3046 | cJSON *a_element = a->child; 3047 | cJSON *b_element = b->child; 3048 | 3049 | for (; (a_element != NULL) && (b_element != NULL);) 3050 | { 3051 | if (!cJSON_Compare(a_element, b_element, case_sensitive)) 3052 | { 3053 | return false; 3054 | } 3055 | 3056 | a_element = a_element->next; 3057 | b_element = b_element->next; 3058 | } 3059 | 3060 | /* one of the arrays is longer than the other */ 3061 | if (a_element != b_element) { 3062 | return false; 3063 | } 3064 | 3065 | return true; 3066 | } 3067 | 3068 | case cJSON_Object: 3069 | { 3070 | cJSON *a_element = NULL; 3071 | cJSON *b_element = NULL; 3072 | cJSON_ArrayForEach(a_element, a) 3073 | { 3074 | /* TODO This has O(n^2) runtime, which is horrible! */ 3075 | b_element = get_object_item(b, a_element->string, case_sensitive); 3076 | if (b_element == NULL) 3077 | { 3078 | return false; 3079 | } 3080 | 3081 | if (!cJSON_Compare(a_element, b_element, case_sensitive)) 3082 | { 3083 | return false; 3084 | } 3085 | } 3086 | 3087 | /* doing this twice, once on a and b to prevent true comparison if a subset of b 3088 | * TODO: Do this the proper way, this is just a fix for now */ 3089 | cJSON_ArrayForEach(b_element, b) 3090 | { 3091 | a_element = get_object_item(a, b_element->string, case_sensitive); 3092 | if (a_element == NULL) 3093 | { 3094 | return false; 3095 | } 3096 | 3097 | if (!cJSON_Compare(b_element, a_element, case_sensitive)) 3098 | { 3099 | return false; 3100 | } 3101 | } 3102 | 3103 | return true; 3104 | } 3105 | 3106 | default: 3107 | return false; 3108 | } 3109 | } 3110 | 3111 | CJSON_PUBLIC(void *) cJSON_malloc(size_t size) 3112 | { 3113 | return global_hooks.allocate(size); 3114 | } 3115 | 3116 | CJSON_PUBLIC(void) cJSON_free(void *object) 3117 | { 3118 | global_hooks.deallocate(object); 3119 | } 3120 | -------------------------------------------------------------------------------- /deps/cJSON/cJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2017 Dave Gamble and cJSON contributors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifndef cJSON__h 24 | #define cJSON__h 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | #if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) 32 | #define __WINDOWS__ 33 | #endif 34 | 35 | #ifdef __WINDOWS__ 36 | 37 | /* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: 38 | 39 | CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols 40 | CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) 41 | CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol 42 | 43 | For *nix builds that support visibility attribute, you can define similar behavior by 44 | 45 | setting default visibility to hidden by adding 46 | -fvisibility=hidden (for gcc) 47 | or 48 | -xldscope=hidden (for sun cc) 49 | to CFLAGS 50 | 51 | then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does 52 | 53 | */ 54 | 55 | #define CJSON_CDECL __cdecl 56 | #define CJSON_STDCALL __stdcall 57 | 58 | /* export symbols by default, this is necessary for copy pasting the C and header file */ 59 | #if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) 60 | #define CJSON_EXPORT_SYMBOLS 61 | #endif 62 | 63 | #if defined(CJSON_HIDE_SYMBOLS) 64 | #define CJSON_PUBLIC(type) type CJSON_STDCALL 65 | #elif defined(CJSON_EXPORT_SYMBOLS) 66 | #define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL 67 | #elif defined(CJSON_IMPORT_SYMBOLS) 68 | #define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL 69 | #endif 70 | #else /* !__WINDOWS__ */ 71 | #define CJSON_CDECL 72 | #define CJSON_STDCALL 73 | 74 | #if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) 75 | #define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type 76 | #else 77 | #define CJSON_PUBLIC(type) type 78 | #endif 79 | #endif 80 | 81 | /* project version */ 82 | #define CJSON_VERSION_MAJOR 1 83 | #define CJSON_VERSION_MINOR 7 84 | #define CJSON_VERSION_PATCH 16 85 | 86 | #include 87 | 88 | /* cJSON Types: */ 89 | #define cJSON_Invalid (0) 90 | #define cJSON_False (1 << 0) 91 | #define cJSON_True (1 << 1) 92 | #define cJSON_NULL (1 << 2) 93 | #define cJSON_Number (1 << 3) 94 | #define cJSON_String (1 << 4) 95 | #define cJSON_Array (1 << 5) 96 | #define cJSON_Object (1 << 6) 97 | #define cJSON_Raw (1 << 7) /* raw json */ 98 | 99 | #define cJSON_IsReference 256 100 | #define cJSON_StringIsConst 512 101 | 102 | /* The cJSON structure: */ 103 | typedef struct cJSON 104 | { 105 | /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ 106 | struct cJSON *next; 107 | struct cJSON *prev; 108 | /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ 109 | struct cJSON *child; 110 | 111 | /* The type of the item, as above. */ 112 | int type; 113 | 114 | /* The item's string, if type==cJSON_String and type == cJSON_Raw */ 115 | char *valuestring; 116 | /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ 117 | int valueint; 118 | /* The item's number, if type==cJSON_Number */ 119 | double valuedouble; 120 | 121 | /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ 122 | char *string; 123 | } cJSON; 124 | 125 | typedef struct cJSON_Hooks 126 | { 127 | /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ 128 | void *(CJSON_CDECL *malloc_fn)(size_t sz); 129 | void (CJSON_CDECL *free_fn)(void *ptr); 130 | } cJSON_Hooks; 131 | 132 | typedef int cJSON_bool; 133 | 134 | /* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. 135 | * This is to prevent stack overflows. */ 136 | #ifndef CJSON_NESTING_LIMIT 137 | #define CJSON_NESTING_LIMIT 1000 138 | #endif 139 | 140 | /* returns the version of cJSON as a string */ 141 | CJSON_PUBLIC(const char*) cJSON_Version(void); 142 | 143 | /* Supply malloc, realloc and free functions to cJSON */ 144 | CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); 145 | 146 | /* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ 147 | /* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ 148 | CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); 149 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); 150 | /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ 151 | /* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ 152 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); 153 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); 154 | 155 | /* Render a cJSON entity to text for transfer/storage. */ 156 | CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); 157 | /* Render a cJSON entity to text for transfer/storage without any formatting. */ 158 | CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); 159 | /* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ 160 | CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); 161 | /* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ 162 | /* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ 163 | CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); 164 | /* Delete a cJSON entity and all subentities. */ 165 | CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); 166 | 167 | /* Returns the number of items in an array (or object). */ 168 | CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); 169 | /* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ 170 | CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); 171 | /* Get item "string" from object. Case insensitive. */ 172 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); 173 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); 174 | CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); 175 | /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ 176 | CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); 177 | 178 | /* Check item type and return its value */ 179 | CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); 180 | CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); 181 | 182 | /* These functions check the type of an item */ 183 | CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); 184 | CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); 185 | CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); 186 | CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); 187 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); 188 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); 189 | CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); 190 | CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); 191 | CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); 192 | CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); 193 | 194 | /* These calls create a cJSON item of the appropriate type. */ 195 | CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); 196 | CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); 197 | CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); 198 | CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); 199 | CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); 200 | CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); 201 | /* raw json */ 202 | CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); 203 | CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); 204 | CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); 205 | 206 | /* Create a string where valuestring references a string so 207 | * it will not be freed by cJSON_Delete */ 208 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); 209 | /* Create an object/array that only references it's elements so 210 | * they will not be freed by cJSON_Delete */ 211 | CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); 212 | CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); 213 | 214 | /* These utilities create an Array of count items. 215 | * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ 216 | CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); 217 | CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); 218 | CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); 219 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); 220 | 221 | /* Append item to the specified array/object. */ 222 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); 223 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); 224 | /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. 225 | * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before 226 | * writing to `item->string` */ 227 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); 228 | /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ 229 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); 230 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); 231 | 232 | /* Remove/Detach items from Arrays/Objects. */ 233 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); 234 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); 235 | CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); 236 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); 237 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); 238 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); 239 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); 240 | 241 | /* Update array items. */ 242 | CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ 243 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); 244 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); 245 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); 246 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); 247 | 248 | /* Duplicate a cJSON item */ 249 | CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); 250 | /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will 251 | * need to be released. With recurse!=0, it will duplicate any children connected to the item. 252 | * The item->next and ->prev pointers are always zero on return from Duplicate. */ 253 | /* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. 254 | * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ 255 | CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); 256 | 257 | /* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. 258 | * The input pointer json cannot point to a read-only address area, such as a string constant, 259 | * but should point to a readable and writable address area. */ 260 | CJSON_PUBLIC(void) cJSON_Minify(char *json); 261 | 262 | /* Helper functions for creating and adding items to an object at the same time. 263 | * They return the added item or NULL on failure. */ 264 | CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); 265 | CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); 266 | CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); 267 | CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); 268 | CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); 269 | CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); 270 | CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); 271 | CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); 272 | CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); 273 | 274 | /* When assigning an integer value, it needs to be propagated to valuedouble too. */ 275 | #define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) 276 | /* helper for the cJSON_SetNumberValue macro */ 277 | CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); 278 | #define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) 279 | /* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ 280 | CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); 281 | 282 | /* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ 283 | #define cJSON_SetBoolValue(object, boolValue) ( \ 284 | (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ 285 | (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ 286 | cJSON_Invalid\ 287 | ) 288 | 289 | /* Macro for iterating over an array or object */ 290 | #define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) 291 | 292 | /* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ 293 | CJSON_PUBLIC(void *) cJSON_malloc(size_t size); 294 | CJSON_PUBLIC(void) cJSON_free(void *object); 295 | 296 | #ifdef __cplusplus 297 | } 298 | #endif 299 | 300 | #endif 301 | -------------------------------------------------------------------------------- /font.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023-2024 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "font.h" 28 | #include "log.h" 29 | 30 | extern SDL_Renderer *renderer; 31 | 32 | /** 33 | * @brief Calculate the length of an UTF-8 encoded string 34 | * 35 | * @param s UTF-8 string. 36 | * 37 | * @return Returns the amount of characters in the 38 | * string. 39 | */ 40 | static inline size_t utf8_strlen(const char *s) 41 | { 42 | size_t len; 43 | len = 0; 44 | while (*s) { 45 | if ((*s & 0xc0) != 0x80) 46 | len++; 47 | s++; 48 | } 49 | return (len); 50 | } 51 | 52 | /** 53 | * @brief For a given UTF-8 encoded text in @p u8_txt, returns 54 | * a new string of @p new_size characters, adding ellipsis 55 | * at the end of the text. 56 | * 57 | * @param u8_txt UTF-8 text to be truncated. 58 | * @param max_size Max size (in bytes) of the truncated text. 59 | * 60 | * @return Returns a new string containing the truncated 61 | * text. 62 | */ 63 | static char *utf8_truncate(const unsigned char *u8_txt, size_t max_size) 64 | { 65 | const unsigned char *s; 66 | size_t codep_cnt; 67 | char *new; 68 | 69 | codep_cnt = 0; 70 | 71 | /* 72 | * Walk 3 codepoints right before the target byte, then 73 | * add our ellipsis. 74 | * 75 | * Since characters might be bigger than others, 3 codepoints 76 | * is not equal to 3 '...', i.e., 3 arbitrary UTF8 chars might 77 | * not have the same width as 3 dots, but most likely will fit 78 | * 3 dots. 79 | */ 80 | s = u8_txt + max_size - 1; 81 | while (s > u8_txt && codep_cnt < 3) { 82 | /* If new codepoint or a single ascii char. */ 83 | if ( ((*s & 0xC0) != 0x80) || (*s <= 127) ) 84 | codep_cnt++; 85 | s--; 86 | max_size--; 87 | } 88 | 89 | /* Allocate new string: utf8 + ... + \0. */ 90 | new = calloc(1, max_size + 3 + 1); 91 | if (!new) 92 | log_oom("Unable to allocate UTF8 string!\n"); 93 | 94 | memcpy(new + 0, u8_txt, max_size); 95 | memcpy(new + max_size, "...", 3); 96 | return (new); 97 | } 98 | 99 | /** 100 | * @brief Initializes the SDL_ttf font lib. 101 | */ 102 | int font_init(void) { 103 | return TTF_Init(); 104 | } 105 | 106 | /** 107 | * @brief De-initializes the SDL_ttf font lib. 108 | */ 109 | void font_quit(void) { 110 | TTF_Quit(); 111 | } 112 | 113 | /** 114 | * @brief Open a given font for a given @p file and 115 | * size @p ptsize. 116 | * 117 | * @param file Font path to be loaded. 118 | * @param ptsize Desired font size. 119 | * 120 | * @return Returns a pointer to the loaded font. 121 | */ 122 | TTF_Font *font_open(const char *file, int ptsize) { 123 | return TTF_OpenFont(file, ptsize); 124 | } 125 | 126 | /** 127 | * @brief Close a previously loaded font. 128 | * 129 | * @param font Loaded font to be closed. 130 | */ 131 | void font_close(TTF_Font *font) { 132 | TTF_CloseFont(font); 133 | } 134 | 135 | /** 136 | * @brief Creates a new SDL_Texture for a given @p text, 137 | * @p color and @p font, returning the result into @p rt. 138 | * 139 | * @param rt Rendered text structure pointer. 140 | * @param font Already opened TTF font. 141 | * @param text Text to be created. 142 | * @param color SDL color 143 | * @param mwidth Maximum text width (in pixels, 0 to not 144 | * check). If the text exceeds the maximum 145 | * size, ellipsis will be added. 146 | * 147 | * @note If there is an previous allocated text, it will 148 | * be destroyed first, so multiples calls to this is 149 | * safe. 150 | */ 151 | void font_create_text(struct rendered_text *rt, TTF_Font *font, 152 | const char *text, const SDL_Color *color, unsigned mwidth) 153 | { 154 | SDL_Surface *s; 155 | char *new_text; 156 | size_t count; 157 | 158 | new_text = NULL; 159 | 160 | if (!rt) 161 | return; 162 | 163 | /* Clear previous text, if any. */ 164 | font_destroy_text(rt); 165 | 166 | /* 167 | * Check maximum width was provided _and_ if the 168 | * the text exceeds the maximum size. 169 | */ 170 | if (mwidth) { 171 | TTF_MeasureString(font, text, 0, mwidth, NULL, &count); 172 | 173 | /* If the reported count (in bytes) is less than 174 | * our string length, truncate it. */ 175 | if (count < strlen(text)) { 176 | new_text = utf8_truncate((const unsigned char*)text, count); 177 | text = new_text; 178 | } 179 | } 180 | 181 | /* Create a new one. */ 182 | s = TTF_RenderText_Blended(font, text, 0, *color); 183 | if (!s) 184 | log_panic("Unable to create font surface!\n"); 185 | 186 | rt->text_texture = SDL_CreateTextureFromSurface(renderer, s); 187 | if (!rt->text_texture) 188 | log_panic("Unable to create font texture!\n"); 189 | 190 | rt->width = s->w; 191 | rt->height = s->h; 192 | SDL_DestroySurface(s); 193 | free(new_text); 194 | } 195 | 196 | /** 197 | * @brief Destroy a previously created text. 198 | * 199 | * @param rt Rendered text to be destroyed. 200 | */ 201 | void font_destroy_text(struct rendered_text *rt) 202 | { 203 | if (!rt || !rt->text_texture) 204 | return; 205 | 206 | SDL_DestroyTexture(rt->text_texture); 207 | rt->text_texture = NULL; 208 | rt->width = 0; 209 | rt->height = 0; 210 | } 211 | 212 | /** 213 | * @brief Copy the text texture pointed by @p rt into the 214 | * renderer, at coordinates @p x and @p y. 215 | * 216 | * This is an small wrapper around 'SDL_RenderTexture', 217 | * with an additional check if the text/texture pointer 218 | * is valid or not, i.e., this function can be called 219 | * at any time, even if the texture does not exist 220 | * (NULL). 221 | * 222 | * @param rt Text to be rendered. 223 | * @param x Screen X coordinate. 224 | * @param y Screen Y coordinate. 225 | */ 226 | void font_render_text(struct rendered_text *rt, int x, int y) 227 | { 228 | if (!rt || !rt->text_texture) 229 | return; 230 | 231 | SDL_FRect rect; 232 | rect.x = x; 233 | rect.y = y; 234 | rect.w = rt->width; 235 | rect.h = rt->height; 236 | SDL_RenderTexture(renderer, rt->text_texture, NULL, &rect); 237 | } 238 | -------------------------------------------------------------------------------- /font.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023-2024 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef FONT_H 26 | #define FONT_H 27 | 28 | #include 29 | 30 | struct rendered_text 31 | { 32 | SDL_Texture *text_texture; 33 | int width; 34 | int height; 35 | }; 36 | 37 | extern int font_init(void); 38 | extern void font_quit(void); 39 | extern TTF_Font *font_open(const char *file, int ptsize); 40 | extern void font_close(TTF_Font *font); 41 | extern void font_create_text(struct rendered_text *rt, TTF_Font *font, 42 | const char *text, const SDL_Color *color, unsigned mwidth); 43 | extern void font_destroy_text(struct rendered_text *rt); 44 | extern void font_render_text(struct rendered_text *rt, int x, int y); 45 | 46 | 47 | #endif /* FONT_H */ 48 | -------------------------------------------------------------------------------- /image.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023-2024 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "log.h" 30 | 31 | #define STBI_ONLY_PNG 32 | #define STB_IMAGE_IMPLEMENTATION 33 | #include "deps/stb_image.h" 34 | 35 | extern SDL_Renderer *renderer; 36 | 37 | /** 38 | * @brief If the texture pointed by @p tex exists, 39 | * free it, otherwise, do nothing. 40 | * 41 | * @param tex Texture pointer. 42 | */ 43 | void image_free(SDL_Texture **tex) 44 | { 45 | if (!*tex) 46 | return; 47 | SDL_DestroyTexture(*tex); 48 | *tex = NULL; 49 | } 50 | 51 | /** 52 | * @brief Load a given image path pointed by @p img, and 53 | * save into the texture pointer pointed by @p tex. 54 | * 55 | * If the texture pointer already points to an existing 56 | * texture, the old texture is deallocated first. 57 | * 58 | * @param tex Texture pointer to be loaded. 59 | * @param img Image path. 60 | */ 61 | void image_load(SDL_Texture **tex, const char *img) 62 | { 63 | int w, h, comp; 64 | SDL_Surface *s; 65 | unsigned char *buff; 66 | 67 | /* Silence 'defined but not used' stb_image warnings. */ 68 | ((void)stbi__addints_valid); 69 | ((void)stbi__mul2shorts_valid); 70 | 71 | image_free(tex); 72 | 73 | comp = 4; 74 | buff = stbi_load(img, &w, &h, &comp, 0); 75 | if (!buff) 76 | log_panic("Unable to load image: %s!\n", img); 77 | 78 | s = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_RGBA32, buff, 4*w); 79 | if (!s) 80 | log_panic("Unable to create image surface!: %s\n", SDL_GetError()); 81 | 82 | *tex = SDL_CreateTextureFromSurface(renderer, s); 83 | if (!*tex) 84 | log_panic("Unable to create image texture!\n"); 85 | 86 | SDL_DestroySurface(s); 87 | stbi_image_free(buff); 88 | } 89 | 90 | /** 91 | * @brief Copy the texture pointed by @p tex into the 92 | * renderer, at coordinates @p x and @p y. 93 | * 94 | * This is an small wrapper around 'SDL_RenderTexture', 95 | * with an additional check if the texture pointer is 96 | * valid or not, i.e., this function can be called 97 | * at any time, even if the texture does not exist 98 | * (NULL). 99 | * 100 | * @param tex Texture to be rendered. 101 | * @param x Screen X coordinate. 102 | * @param y Screen Y coordinate. 103 | */ 104 | void image_render(SDL_Texture *tex, int x, int y) 105 | { 106 | SDL_FRect rect; 107 | float w, h; 108 | 109 | if (!tex) 110 | return; 111 | 112 | SDL_GetTextureSize(tex, &w, &h); 113 | rect.x = x; 114 | rect.y = y; 115 | rect.w = w; 116 | rect.h = h; 117 | SDL_RenderTexture(renderer, tex, NULL, &rect); 118 | } 119 | -------------------------------------------------------------------------------- /image.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef IMAGE_H 26 | #define IMAGE_H 27 | 28 | extern void image_free(SDL_Texture **tex); 29 | extern void image_load(SDL_Texture **tex, 30 | const char *img); 31 | extern void image_render(SDL_Texture *tex, int x, int y); 32 | 33 | #endif /* IMAGE_H */ 34 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "log.h" 32 | 33 | /** 34 | * @brief Get the current timestamp (HH:MM:SS) 35 | * as a constant string. 36 | * 37 | * If success, returns the timestamp, otherwise, 38 | * NULL. 39 | */ 40 | static const char *get_timestamp(void) 41 | { 42 | time_t now; 43 | struct tm *now_tm; 44 | static char buffer[64]; 45 | 46 | now = time(NULL); 47 | now_tm = localtime(&now); 48 | 49 | SDL_memset(buffer, 0, sizeof(buffer)); 50 | if (!strftime(buffer, sizeof(buffer), "%X", now_tm)) 51 | return (NULL); 52 | 53 | return (buffer); 54 | } 55 | 56 | /** 57 | * @brief Logs a given message using the SDL logging 58 | * routines but printing the timestamp too. 59 | * 60 | * @param priority SDL priority message type. 61 | * @param fmt String format. 62 | */ 63 | void log_msg(int priority, 64 | SDL_PRINTF_FORMAT_STRING const char *fmt, ...) 65 | { 66 | va_list list; 67 | char log[2048]; 68 | 69 | /* Print log message into a buffer */ 70 | SDL_memset(log, 0, sizeof log); 71 | va_start(list, fmt); 72 | (void)SDL_vsnprintf(log, sizeof(log) - 1, fmt, list); 73 | va_end(list); 74 | 75 | /* Log with timestamp and newline */ 76 | SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, 77 | priority, "(%s) %s", get_timestamp(), log); 78 | } 79 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef LOG_H 26 | #define LOG_H 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | extern void log_msg(int priority, 33 | SDL_PRINTF_FORMAT_STRING const char *fmt, ...); 34 | 35 | /* Log messages. */ 36 | #define log_oom(msg) \ 37 | do {\ 38 | write(STDOUT_FILENO, "Out of Memory: ", 15); \ 39 | write(STDOUT_FILENO, (msg), strlen(msg)); \ 40 | _exit(1); \ 41 | } while (0) 42 | 43 | #define log_panic(...) \ 44 | do {\ 45 | log_msg(SDL_LOG_PRIORITY_CRITICAL, __VA_ARGS__); \ 46 | exit(1); \ 47 | } while (0) 48 | 49 | #define log_err_to(lbl, ...) \ 50 | do {\ 51 | log_msg(SDL_LOG_PRIORITY_ERROR, __VA_ARGS__); \ 52 | goto lbl; \ 53 | } while (0) 54 | 55 | #define log_info(...) log_msg(SDL_LOG_PRIORITY_INFO, __VA_ARGS__) 56 | 57 | #endif /* LOG_H. */ 58 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023-2024 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "font.h" 34 | #include "weather.h" 35 | #include "image.h" 36 | #include "log.h" 37 | 38 | /* Window size. */ 39 | #define SCREEN_WIDTH 341 40 | #define SCREEN_HEIGHT 270 41 | 42 | SDL_Renderer *renderer; 43 | static SDL_Window *window; 44 | 45 | /* Background image. */ 46 | static SDL_Texture *bg_tex; 47 | 48 | /* Icon background (sun, moon...). */ 49 | static SDL_Texture *bg_icon_tex; 50 | 51 | /* Loaded fonts and rendered texts. */ 52 | static TTF_Font *font_16pt; 53 | static TTF_Font *font_18pt; 54 | static TTF_Font *font_40pt; 55 | static struct rendered_text txt_footer; 56 | static struct rendered_text txt_day1, txt_day2, txt_day3; 57 | static struct rendered_text txt_day1_min, txt_day1_max; 58 | static struct rendered_text txt_day2_min, txt_day2_max; 59 | static struct rendered_text txt_day3_min, txt_day3_max; 60 | static struct rendered_text txt_location; 61 | static struct rendered_text txt_curr_minmax; 62 | static struct rendered_text txt_curr_cond; 63 | static struct rendered_text txt_curr_temp; 64 | 65 | /* Forecast icons textures. */ 66 | static SDL_Texture *fc_day1_tex; 67 | static SDL_Texture *fc_day2_tex; 68 | static SDL_Texture *fc_day3_tex; 69 | 70 | /* Text colors. */ 71 | static SDL_Color color_blue = {148,199,228,SDL_ALPHA_OPAQUE}; 72 | static SDL_Color color_white = {255,255,255,SDL_ALPHA_OPAQUE}; 73 | static SDL_Color color_gray = {146,148,149,SDL_ALPHA_OPAQUE}; 74 | static SDL_Color color_cloudy_gray = {162,179,189,SDL_ALPHA_OPAQUE}; 75 | static SDL_Color color_black = {0,0,0,SDL_ALPHA_OPAQUE}; 76 | 77 | /* Footer text, i.e., where the weather data 78 | * were obtained. */ 79 | #define FOOTER_X 21 80 | #define FOOTER_Y 222 81 | #define FOOTER_MAX_WIDTH 292 82 | 83 | /* Forecast days. */ 84 | #define DAY_Y 151 /* Y-axis for the days text. */ 85 | #define DAY1_X 16 /* Day 1 text forecast X-axis. */ 86 | #define DAY2_X 116 /* Day 2 text forecast X-axis. */ 87 | #define DAY3_X 230 /* Day 3 text forecast X-axis. */ 88 | #define DAYMAX_Y 171 /* Day max 1-2-3 text Y-axis. */ 89 | #define DAYMIN_Y 189 /* Day min 1-2-3 text Y-axis. */ 90 | 91 | #define DAY_IMG_Y 165 /* Forecast images Y-axis. */ 92 | #define DAY1_IMG_X 45 /* Forecast day1 img X-axis. */ 93 | #define DAY2_IMG_X 146 /* Forecast day1 img X-axis. */ 94 | #define DAY3_IMG_X 257 /* Forecast day1 img X-axis. */ 95 | 96 | /* 97 | * Header values 98 | * 99 | * Max X value to the header text, 100 | * which includes: 101 | * - Current temperature 102 | * - Weather condition 103 | * - Min/max temperature 104 | * - Location 105 | * 106 | * X value is dynamically calculated 107 | */ 108 | #define HDR_MAX_X 310 109 | 110 | /* Header Y-values. */ 111 | #define HDR_TEMP_Y 15 112 | #define HDR_COND_Y 60 113 | #define HDR_MINMAX_Y 83 114 | #define HDR_LOC_Y 120 115 | #define HDR_MAX_WIDTH FOOTER_MAX_WIDTH 116 | 117 | /* Current weather info. */ 118 | static struct weather_info wi = {0}; 119 | 120 | /* Command-line arguments. */ 121 | static struct args { 122 | const char *execute_command; 123 | Uint32 update_weather_time_ms; 124 | int x; 125 | int y; 126 | } args = { 127 | .execute_command = NULL, 128 | .update_weather_time_ms = 600*1000, 129 | .x = -1, 130 | .y = -1 131 | }; 132 | 133 | /* Forward definitions. */ 134 | static void create_texts( 135 | const SDL_Color *days_color, 136 | const SDL_Color *max_temp_color, 137 | const SDL_Color *hdr_color); 138 | static Uint32 update_weather_cb(void *userdata, 139 | SDL_TimerID timerID, Uint32 interval); 140 | 141 | /** 142 | * Update logic and drawing for each frame 143 | */ 144 | static inline void update_frame(void) 145 | { 146 | /* Draw background alpha image. */ 147 | SDL_RenderClear(renderer); 148 | 149 | /* Backgruond and icon. */ 150 | SDL_RenderTexture(renderer, bg_tex, NULL, NULL); 151 | image_render(bg_icon_tex, 0,0); 152 | 153 | /* Footer and forecast days text. */ 154 | font_render_text(&txt_footer, FOOTER_X, FOOTER_Y); 155 | font_render_text(&txt_day1, DAY1_X, DAY_Y); 156 | font_render_text(&txt_day2, DAY2_X, DAY_Y); 157 | font_render_text(&txt_day3, DAY3_X, DAY_Y); 158 | 159 | /* Forecast days min and max temp. */ 160 | font_render_text(&txt_day1_max, DAY1_X, DAYMAX_Y); 161 | font_render_text(&txt_day2_max, DAY2_X, DAYMAX_Y); 162 | font_render_text(&txt_day3_max, DAY3_X, DAYMAX_Y); 163 | font_render_text(&txt_day1_min, DAY1_X, DAYMIN_Y); 164 | font_render_text(&txt_day2_min, DAY2_X, DAYMIN_Y); 165 | font_render_text(&txt_day3_min, DAY3_X, DAYMIN_Y); 166 | 167 | /* Header: curr temp, condition, min/max and location. */ 168 | font_render_text(&txt_curr_temp, HDR_MAX_X - txt_curr_temp.width, HDR_TEMP_Y); 169 | font_render_text(&txt_curr_cond, HDR_MAX_X - txt_curr_cond.width, HDR_COND_Y); 170 | font_render_text(&txt_curr_minmax, HDR_MAX_X - txt_curr_minmax.width, HDR_MINMAX_Y); 171 | font_render_text(&txt_location, HDR_MAX_X - txt_location.width, HDR_LOC_Y); 172 | 173 | /* Forecast icons based on weather condition. */ 174 | image_render(fc_day1_tex, DAY1_IMG_X, DAY_IMG_Y); 175 | image_render(fc_day2_tex, DAY2_IMG_X, DAY_IMG_Y); 176 | image_render(fc_day3_tex, DAY3_IMG_X, DAY_IMG_Y); 177 | 178 | /* Render everything. */ 179 | SDL_RenderPresent(renderer); 180 | } 181 | 182 | /** 183 | * @brief Creates the current SDL window and renderer 184 | * with given width @p w, height @P h and @p flags. 185 | * 186 | * @param w Window width. 187 | * @param h Window height. 188 | * @param flags Window flags. 189 | */ 190 | static int create_sdl_window(int w, int h, int flags) 191 | { 192 | if (!SDL_CreateWindowAndRenderer( 193 | "windy", w, h, flags, &window, &renderer)) 194 | log_panic("Unable to create window and renderer!\n"); 195 | 196 | SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); 197 | 198 | /* Set coordinates. */ 199 | if (args.x >= 0 && args.y >= 0) 200 | SDL_SetWindowPosition(window, args.x, args.y); 201 | 202 | return (0); 203 | } 204 | 205 | /** 206 | * @brief 'Main' weather update routine. 207 | * 208 | * Executes the command given, read its output in stdout, 209 | * parse its json and then choose which text/icons should 210 | * be loaded into the screen. 211 | */ 212 | static void update_weather_info(void) 213 | { 214 | const SDL_Color *cd, *cmt, *chdr; 215 | const char *bg_icon_tex_path; 216 | const char *wbg; 217 | 218 | char buff1[32] = {0}; 219 | char buff2[32] = {0}; 220 | char buff3[32] = {0}; 221 | 222 | log_info("Updating weather info...\n"); 223 | 224 | if (weather_get(args.execute_command, &wi) < 0) 225 | log_err_to(out, "Unable to get weather info!\n"); 226 | 227 | /* Icon to be loaded if not 'clear'. */ 228 | snprintf(buff1, 229 | sizeof buff1, 230 | "assets/bg_icon_%s.png", 231 | wi.condition); 232 | 233 | bg_icon_tex_path = buff1; 234 | 235 | /* Load background depending of time and weather condition. */ 236 | if (!weather_is_day()) { 237 | wbg = "assets/bg_night.png"; 238 | 239 | /* 240 | * Load moon or other weather icons, accordingly 241 | * to the weather condition. 242 | */ 243 | if (!strcmp(wi.condition, "clear")) 244 | bg_icon_tex_path = weather_get_moon_phase_icon(); 245 | 246 | cd = &color_gray; 247 | cmt = &color_white; 248 | chdr = &color_white; 249 | } 250 | 251 | /* If day. */ 252 | else { 253 | /* If clear. */ 254 | if (!strcmp(wi.condition, "clear")) { 255 | bg_icon_tex_path = NULL; 256 | wbg = "assets/bg_sunny_day.png"; 257 | cd = &color_blue; 258 | cmt = &color_white; 259 | chdr = &color_black; 260 | } 261 | 262 | /* Anything else, should load bg and icon. */ 263 | else { 264 | wbg = "assets/bg_notclear_day.png"; 265 | cd = &color_cloudy_gray; 266 | cmt = &color_white; 267 | chdr = &color_black; 268 | } 269 | } 270 | 271 | image_free(&bg_icon_tex); 272 | image_load(&bg_tex, wbg); 273 | if (bg_icon_tex_path) 274 | image_load(&bg_icon_tex, bg_icon_tex_path); 275 | 276 | create_texts(cd, cmt, chdr); 277 | 278 | /* Forecast days icons. */ 279 | snprintf(buff1, sizeof buff1, "assets/%s.png", 280 | wi.forecast[0].condition); 281 | snprintf(buff2, sizeof buff2, "assets/%s.png", 282 | wi.forecast[1].condition); 283 | snprintf(buff3, sizeof buff3, "assets/%s.png", 284 | wi.forecast[2].condition); 285 | 286 | image_load(&fc_day1_tex, buff1); 287 | image_load(&fc_day2_tex, buff2); 288 | image_load(&fc_day3_tex, buff3); 289 | 290 | out: 291 | SDL_AddTimer(args.update_weather_time_ms, 292 | update_weather_cb, NULL); 293 | } 294 | 295 | /** 296 | * @brief Load the same font in three diferent sizes 297 | * for the GUI texts. 298 | */ 299 | static void load_fonts(void) 300 | { 301 | /* Load require fonts and sizes. */ 302 | font_16pt = font_open("assets/fonts/NotoSans-Regular.ttf", 16); 303 | if (!font_16pt) 304 | log_panic("Unable to open font with size 16pt!\n"); 305 | font_18pt = font_open("assets/fonts/NotoSans-Regular.ttf", 18); 306 | if (!font_18pt) 307 | log_panic("Unable to open font with size 18pt!\n"); 308 | font_40pt = font_open("assets/fonts/NotoSans-Regular.ttf", 40); 309 | if (!font_40pt) 310 | log_panic("Unable to open font with size 40pt!\n"); 311 | } 312 | 313 | /** 314 | * @brief Create all texts/textures to the GUI. 315 | * 316 | * @param days_color Color of footer, days and min temp. 317 | * @param max_temp_color Color of the max temp color for the 318 | * forecast days. 319 | * @param hdr_color Header color (curr temp, location...) 320 | */ 321 | static void create_texts( 322 | const SDL_Color *days_color, 323 | const SDL_Color *max_temp_color, 324 | const SDL_Color *hdr_color) 325 | { 326 | int d1, d2, d3; 327 | char buff1[32] = {0}; 328 | char buff2[32] = {0}; 329 | char buff3[32] = {0}; 330 | 331 | static const char *const days_of_week[] = { 332 | "sunday", 333 | "monday", 334 | "tuesday", 335 | "wednesday", 336 | "thursday", 337 | "friday", 338 | "saturday" 339 | }; 340 | 341 | weather_get_forecast_days(&d1, &d2, &d3); 342 | 343 | /* Footer. */ 344 | font_create_text(&txt_footer, font_16pt, wi.provider, days_color, 345 | FOOTER_MAX_WIDTH); 346 | 347 | /* Forecast days string. */ 348 | font_create_text(&txt_day1, font_16pt, days_of_week[d1], days_color, 0); 349 | font_create_text(&txt_day2, font_16pt, days_of_week[d2], days_color, 0); 350 | font_create_text(&txt_day3, font_16pt, days_of_week[d3], days_color, 0); 351 | 352 | /* Max temperature value. */ 353 | snprintf(buff1, sizeof buff1, "%dº", wi.forecast[0].max_temp); 354 | snprintf(buff2, sizeof buff2, "%dº", wi.forecast[1].max_temp); 355 | snprintf(buff3, sizeof buff3, "%dº", wi.forecast[2].max_temp); 356 | 357 | font_create_text(&txt_day1_max, font_16pt, buff1, max_temp_color, 0); 358 | font_create_text(&txt_day2_max, font_16pt, buff2, max_temp_color, 0); 359 | font_create_text(&txt_day3_max, font_16pt, buff3, max_temp_color, 0); 360 | 361 | /* Min temperature value. */ 362 | snprintf(buff1, sizeof buff1, "%dº", wi.forecast[0].min_temp); 363 | snprintf(buff2, sizeof buff2, "%dº", wi.forecast[1].min_temp); 364 | snprintf(buff3, sizeof buff3, "%dº", wi.forecast[2].min_temp); 365 | 366 | font_create_text(&txt_day1_min, font_16pt, buff1, days_color, 0); 367 | font_create_text(&txt_day2_min, font_16pt, buff2, days_color, 0); 368 | font_create_text(&txt_day3_min, font_16pt, buff3, days_color, 0); 369 | 370 | /* Header: location, max/min, current condition and temperature. */ 371 | snprintf(buff1, sizeof buff1, "%dº - %dº", wi.max_temp, wi.min_temp); 372 | snprintf(buff2, sizeof buff2, "%c%s", 373 | toupper(wi.condition[0]), wi.condition+1); 374 | snprintf(buff3, sizeof buff3, "%dº", wi.temperature); 375 | 376 | font_create_text(&txt_location, font_18pt, wi.location, hdr_color, 377 | HDR_MAX_WIDTH); 378 | font_create_text(&txt_curr_minmax, font_18pt, buff1, hdr_color, 0); 379 | font_create_text(&txt_curr_cond, font_18pt, buff2, hdr_color, 0); 380 | font_create_text(&txt_curr_temp, font_40pt, buff3, hdr_color, 0); 381 | } 382 | 383 | /** 384 | * @brief Free all fonts and textures used. 385 | */ 386 | void free_resources(void) 387 | { 388 | /* Free textures. */ 389 | image_free(&bg_tex); 390 | image_free(&bg_icon_tex); 391 | image_free(&fc_day1_tex); 392 | image_free(&fc_day2_tex); 393 | image_free(&fc_day3_tex); 394 | font_destroy_text(&txt_footer); 395 | font_destroy_text(&txt_day1); 396 | font_destroy_text(&txt_day2); 397 | font_destroy_text(&txt_day3); 398 | font_destroy_text(&txt_day1_max); 399 | font_destroy_text(&txt_day2_max); 400 | font_destroy_text(&txt_day3_max); 401 | font_destroy_text(&txt_day1_min); 402 | font_destroy_text(&txt_day2_min); 403 | font_destroy_text(&txt_day3_min); 404 | font_destroy_text(&txt_curr_temp); 405 | font_destroy_text(&txt_curr_cond); 406 | font_destroy_text(&txt_curr_minmax); 407 | font_destroy_text(&txt_location); 408 | font_destroy_text(&txt_curr_temp); 409 | 410 | /* Close loaded fonts. */ 411 | font_close(font_16pt); 412 | font_close(font_18pt); 413 | font_close(font_40pt); 414 | } 415 | 416 | /** 417 | * @brief SDL timer callback to update the weather 418 | * 419 | * @param userdata Custom data 420 | * @param timerID Current timer being processed. 421 | * @param interval Current timer interval 422 | * @return Returns the time of the next callback, 0 423 | * to disable. 424 | */ 425 | static Uint32 update_weather_cb(void *userdata, SDL_TimerID timerID, Uint32 interval) 426 | { 427 | ((void)userdata); 428 | ((void)timerID); 429 | ((void)interval); 430 | SDL_Event event; 431 | event.type = SDL_EVENT_USER; 432 | event.user.data1 = NULL; 433 | SDL_PushEvent(&event); 434 | return (0); 435 | } 436 | 437 | /** 438 | * @brief Show program usage. 439 | * @param prgname Program name. 440 | */ 441 | void usage(const char *prgname) 442 | { 443 | fprintf(stderr, "Usage: %s [options] -c \n", 444 | prgname); 445 | fprintf(stderr, 446 | "Options:\n" 447 | " -t Interval time (in seconds) to check for weather\n" 448 | " updates (default = 10 minutes)\n" 449 | " -c Command to execute when the update time reaches\n" 450 | " -x Set the window X coordinate\n" 451 | " -y Set the window Y coordinate\n" 452 | " -h This help\n\n" 453 | "Example:\n" 454 | " Update the weather info each 30 minutes, by running the command\n" 455 | " 'python request.py'\n" 456 | " $ %s -t 1800 -c \"python request.py\"\n\n" 457 | "Obs: Options -t,-x and -y are not required, -c is required!\n", 458 | prgname); 459 | exit(EXIT_FAILURE); 460 | } 461 | 462 | /** 463 | * @brief Parse command-line arguments. 464 | * 465 | * @param argc Argument count. 466 | * @param argv Argument list. 467 | */ 468 | void parse_args(int argc, char **argv) 469 | { 470 | int c; /* Current arg. */ 471 | while ((c = getopt(argc, argv, "t:c:x:y:h")) != -1) 472 | { 473 | switch (c) { 474 | case 'h': 475 | usage(argv[0]); 476 | break; 477 | case 't': 478 | args.update_weather_time_ms = atoi(optarg)*1000; 479 | if (!args.update_weather_time_ms) { 480 | log_info("Invalid -t value, please choose a valid interval!\n"); 481 | usage(argv[0]); 482 | } 483 | break; 484 | case 'c': 485 | args.execute_command = optarg; 486 | break; 487 | case 'x': 488 | args.x = atoi(optarg); 489 | break; 490 | case 'y': 491 | args.y = atoi(optarg); 492 | break; 493 | default: 494 | usage(argv[0]); 495 | break; 496 | } 497 | } 498 | 499 | if (!args.execute_command) { 500 | log_info("Option -c is required!\n"); 501 | usage(argv[0]); 502 | } 503 | } 504 | 505 | /** 506 | * Main weather loop. 507 | */ 508 | int main(int argc, char **argv) 509 | { 510 | const char *base_path; 511 | SDL_Event event; 512 | 513 | parse_args(argc, argv); 514 | 515 | /* 516 | * By default, SDL disables the use of a screensaver and 517 | * _also_ the monitor to go into standby while an 518 | * application is running. Since Windy is a desktop widget, 519 | * this default behavior is not what you want and the SDL 520 | * needs to allow screensavers and standby mode to work as 521 | * expected. 522 | */ 523 | SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); 524 | 525 | /* Initialize. */ 526 | if (!SDL_Init(SDL_INIT_VIDEO)) 527 | log_panic("SDL could not initialize!: %s\n", SDL_GetError()); 528 | if (font_init() < 0) 529 | log_panic("Unable to initialize SDL_ttf!\n"); 530 | 531 | base_path = SDL_GetBasePath(); 532 | if (!base_path) 533 | log_panic("Unable to get program base path!\n"); 534 | chdir(base_path); 535 | 536 | create_sdl_window( 537 | SCREEN_WIDTH, SCREEN_HEIGHT, 538 | SDL_WINDOW_TRANSPARENT| 539 | SDL_WINDOW_BORDERLESS| 540 | SDL_WINDOW_UTILITY); 541 | 542 | image_load(&bg_tex, "assets/bg_sunny_day.png"); 543 | load_fonts(); 544 | 545 | update_weather_info(); 546 | 547 | /* Ignore some events that might wake us up 548 | * everytime. */ 549 | SDL_SetEventEnabled(SDL_EVENT_CLIPBOARD_UPDATE, 0); 550 | 551 | while (1) 552 | { 553 | while (SDL_WaitEvent(&event) != 0) { 554 | if (event.type == SDL_EVENT_QUIT) 555 | goto quit; 556 | else if (event.type == SDL_EVENT_USER) { 557 | update_weather_info(); 558 | update_frame(); 559 | } 560 | 561 | /* Only redraw if there is a WINDOW* or DISPLAY* 562 | * event, as most events are unrelated to us. */ 563 | else if 564 | ((event.type >= SDL_EVENT_WINDOW_FIRST && 565 | event.type <= SDL_EVENT_WINDOW_LAST) || 566 | ((event.type >= SDL_EVENT_DISPLAY_FIRST && 567 | event.type <= SDL_EVENT_DISPLAY_LAST))) 568 | { 569 | update_frame(); 570 | } 571 | } 572 | } 573 | 574 | quit: 575 | free_resources(); 576 | 577 | if (renderer) 578 | SDL_DestroyRenderer(renderer); 579 | if (window) 580 | SDL_DestroyWindow(window); 581 | 582 | font_quit(); 583 | SDL_Quit(); 584 | return (0); 585 | } 586 | -------------------------------------------------------------------------------- /request.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # MIT License 4 | # 5 | # Copyright (c) 2023 Davidson Francis 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | import requests 26 | import json 27 | 28 | # 29 | # Example: 30 | # LATITUDE="35.69" 31 | # LONGITUDE="139.69" 32 | # LOCATION="Tokyo, Japan" 33 | # 34 | LATITUDE="" 35 | LONGITUDE="" 36 | LOCATION="" 37 | 38 | # 39 | # OpenMeteo provider, more info at: 40 | # https://open-meteo.com/en/docs 41 | # 42 | PROVIDER="OpenMeteo" 43 | 44 | # Function to convert weather code to a human-readable condition 45 | def get_weather_condition(code): 46 | conditions = { 47 | 0: "clear", # Clear sky 48 | 1: "clear", # Mainly clear 49 | 2: "clear", # Partly cloudy 50 | 3: "clouds", # Overcast 51 | 45: "fog", # Fog 52 | 48: "fog", # Depositing rime fog 53 | 51: "showers", # Drizzle: Light intensity 54 | 53: "showers", # Drizzle: Moderate intensity 55 | 55: "showers", # Drizzle: Dense intensity 56 | 56: "showers", # Freezing Drizzle: Light intensity 57 | 57: "showers", # Freezing Drizzle: Dense intensity 58 | 61: "rainfall", # Rain: Slight intensity 59 | 63: "rainfall", # Rain: Moderate intensity 60 | 65: "rainfall", # Rain: Heavy intensity 61 | 66: "rainfall", # Freezing Rain: Light intensity 62 | 67: "rainfall", # Freezing Rain: Heavy intensity 63 | 71: "snow", # Snow fall: Slight intensity 64 | 73: "snow", # Snow fall: Moderate intensity 65 | 75: "snow", # Snow fall: Heavy intensity 66 | 77: "snow", # Snow grains 67 | 80: "rainfall", # Rain showers: Slight intensity 68 | 81: "rainfall", # Rain showers: Moderate intensity 69 | 82: "rainfall", # Rain showers: Violent intensity 70 | 85: "snow", # Snow showers: Slight intensity 71 | 86: "snow", # Snow showers: Heavy intensity 72 | 95: "thunder", # Thunderstorm: Slight or moderate 73 | 96: "thunder", # Thunderstorm: With slight hail 74 | 99: "thunder" # Thunderstorm: With heavy hail 75 | } 76 | return conditions.get(code, "unknown") 77 | 78 | # Function to format forecast data 79 | def format_forecast(forecast_data): 80 | forecast = [] 81 | # Skip the first element (current day) in the forecast array 82 | for i in range(1, 4): 83 | data = { 84 | "max_temp": forecast_data["temperature_2m_max"][i], 85 | "min_temp": forecast_data["temperature_2m_min"][i], 86 | "condition": get_weather_condition(forecast_data["weathercode"][i]), 87 | } 88 | forecast.append(data) 89 | return forecast 90 | 91 | # HTTPS request to get JSON data 92 | response = requests.get("https://api.open-meteo.com/v1/forecast?" 93 | + "latitude=" + LATITUDE 94 | + "&longitude=" + LONGITUDE 95 | + "¤t_weather=true" 96 | + "&daily=weathercode,temperature_2m_max,temperature_2m_min&timezone=auto&forecast_days=4") 97 | 98 | data = response.json() 99 | 100 | # Process data and create the output JSON 101 | output = { 102 | "temperature": data["current_weather"]["temperature"], 103 | "condition": get_weather_condition(data["current_weather"]["weathercode"]), 104 | "max_temp": data["daily"]["temperature_2m_max"][0], 105 | "min_temp": data["daily"]["temperature_2m_min"][0], 106 | "location": LOCATION, 107 | "provider": PROVIDER, 108 | "forecast": format_forecast(data["daily"]), 109 | } 110 | 111 | # Encode the output JSON with proper formatting 112 | output_json = json.dumps(output, indent=4) 113 | 114 | # Print the output JSON to stdout 115 | print(output_json) 116 | -------------------------------------------------------------------------------- /weather.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "deps/cJSON/cJSON.h" 32 | #include "weather.h" 33 | #include "log.h" 34 | 35 | #define LUNAR_CYCLE_CONSTANT 29.53058770576 36 | #define BUF_CAPACITY 16 37 | 38 | /* Append buffer. */ 39 | struct abuf { 40 | char *str; 41 | size_t len; 42 | size_t capacity; 43 | }; 44 | 45 | /* Moon phases path. */ 46 | const char* moon_phases[] = { 47 | "assets/bg_icon_new_moon.png", 48 | "assets/bg_icon_first_quarter.png", 49 | "assets/bg_icon_full_moon.png", 50 | "assets/bg_icon_last_quarter.png", 51 | }; 52 | 53 | /** 54 | * Rounds up to the next power of two. 55 | * @param target Target number to be rounded. 56 | * @returns The next power of two. 57 | */ 58 | static size_t round_power(size_t target) 59 | { 60 | target--; 61 | target |= target >> 1; 62 | target |= target >> 2; 63 | target |= target >> 4; 64 | target |= target >> 8; 65 | target |= target >> 16; 66 | target++; 67 | return (target); 68 | } 69 | 70 | /** 71 | * @brief Initializes the append buffer. 72 | * 73 | * @param ab Append buffer pointer to be initialized. 74 | * 75 | * @return Returns 0 if success, -1 otherwise. 76 | */ 77 | static int abuf_alloc(struct abuf *ab) 78 | { 79 | if (!ab) 80 | return (-1); 81 | 82 | ab->capacity = BUF_CAPACITY; 83 | ab->len = 0; 84 | ab->str = calloc(1, BUF_CAPACITY); 85 | if (!ab->str) 86 | log_oom("Unable to allocate abuf!\n"); 87 | 88 | return (0); 89 | } 90 | 91 | /** 92 | * @brief Free the append buffer. 93 | * 94 | * @param ab Append buffer pointer. 95 | */ 96 | static void abuf_free(struct abuf *ab) 97 | { 98 | if (!ab) 99 | return; 100 | 101 | free(ab->str); 102 | ab->capacity = 0; 103 | ab->len = 0; 104 | } 105 | 106 | /** 107 | * @brief Adds a new buffer @p buf of length @p len into 108 | * the append buffer @p ab. 109 | * 110 | * @param ab Append buffer. 111 | * @param buf Buffer to be add. 112 | * @param len Buffer length. 113 | * 114 | * @return Returns 0 if success, -1 otherwise. 115 | */ 116 | static int abuf_append(struct abuf *ab, const char *buf, size_t len) 117 | { 118 | size_t size; 119 | char *ptr; 120 | 121 | if (!ab) 122 | return (-1); 123 | 124 | if (!ab->str && abuf_alloc(ab) < 0) 125 | return (-1); 126 | 127 | size = ab->capacity; 128 | 129 | /* 130 | * Check if there is enough space to fit the new 131 | * buf + NULL terminator, if not, increase it. 132 | * 133 | * Although this is a generic append buffer, its 134 | * always interesting to keep space for a NUL- 135 | * terminator, just in case it is used for 136 | * strings. 137 | */ 138 | if (len >= (ab->capacity - ab->len - 1)) 139 | { 140 | size = round_power(ab->len + len + 1); 141 | ptr = realloc(ab->str, size); 142 | if (!ptr) 143 | log_oom("Unable to increase append buffer!\n"); 144 | 145 | ab->str = ptr; 146 | } 147 | 148 | memcpy(ab->str+ab->len, buf, len); 149 | ab->len += len; 150 | ab->capacity = size; 151 | ab->str[ab->len] = '\0'; 152 | return (0); 153 | } 154 | 155 | /** 156 | * @brief Check if a given weather condition 157 | * is valid or not. 158 | * 159 | * @param condition Weather condition string. 160 | * 161 | * @return Returns 1 if valid, 0 otherwise. 162 | */ 163 | static int is_condition_valid(const char *condition) 164 | { 165 | int ok = 0; 166 | 167 | if (!strcmp(condition, "clear")) 168 | ok = 1; 169 | else if (!strcmp(condition, "fog")) 170 | ok = 1; 171 | else if (!strcmp(condition, "clouds")) 172 | ok = 1; 173 | else if (!strcmp(condition, "showers")) 174 | ok = 1; 175 | else if (!strcmp(condition, "rainfall")) 176 | ok = 1; 177 | else if (!strcmp(condition, "thunder")) 178 | ok = 1; 179 | else if (!strcmp(condition, "snow")) 180 | ok = 1; 181 | 182 | if (!ok) 183 | log_info("Condition '%s' is invalid, acceptable values are:\n" 184 | " clear, fog, clouds, showers, rainfall, thunder, snow\n", 185 | condition); 186 | 187 | return (ok); 188 | } 189 | 190 | /** 191 | * @brief Given a cSON object pointed by @p root, read 192 | * its @p item as a number and saves into @p dest. 193 | * 194 | * @param root JSON root node. 195 | * @param item Item name to be read. 196 | * @param dest Destination integer pointer. 197 | * 198 | * @return Returns 0 if success, -1 otherwise. 199 | */ 200 | static int json_get_number( 201 | const cJSON *root, const char *item, int *dest) 202 | { 203 | cJSON *number; 204 | number = cJSON_GetObjectItemCaseSensitive(root, item); 205 | if (!cJSON_IsNumber(number)) 206 | log_err_to(out0, "'%s' value not found and/or is invalid!\n", item); 207 | *dest = number->valueint; 208 | return (0); 209 | out0: 210 | return (-1); 211 | } 212 | 213 | /** 214 | * @brief Given a cSON object pointed by @p root, read 215 | * its @p item as a string and saves into @p dest. 216 | * 217 | * @param root JSON root node. 218 | * @param item Item name to be read. 219 | * @param dest Destination string pointer. 220 | * 221 | * @return Returns 0 if success, -1 otherwise. 222 | */ 223 | static int json_get_string( 224 | const cJSON *root, const char *item, char **dest) 225 | { 226 | cJSON *str; 227 | str = cJSON_GetObjectItemCaseSensitive(root, item); 228 | if (!cJSON_IsString(str) || !str->valuestring) 229 | log_err_to(out0, "'%s' value not found and/or is invalid!\n", item); 230 | *dest = strdup(str->valuestring); 231 | if (!*dest) 232 | log_oom("Unable to allocate string\n"); 233 | return (0); 234 | out0: 235 | return (-1); 236 | } 237 | 238 | /** 239 | * @brief Parses the received json in @p json_str into the 240 | * structure weather_info pointed by @p wi. 241 | * 242 | * This json (and structure) contains all elements to 243 | * show into the screen. 244 | * 245 | * @return Returns 0 if the parsing was succeeded, -1 if not. 246 | */ 247 | static int json_parse_weather(const char *json_str, 248 | struct weather_info *wi) 249 | { 250 | int i; 251 | cJSON *day; 252 | cJSON *weather; 253 | cJSON *forecast; 254 | const char *error_ptr; 255 | 256 | if (!(weather = cJSON_Parse(json_str))) { 257 | if ((error_ptr = cJSON_GetErrorPtr())) 258 | log_err_to(out0, "Error: %s\n", error_ptr); 259 | else 260 | log_err_to(out0, "Error while parsing json!\n"); 261 | } 262 | 263 | if (json_get_number(weather, "temperature", &wi->temperature) < 0) 264 | goto out0; 265 | if (json_get_number(weather, "max_temp", &wi->max_temp) < 0) 266 | goto out0; 267 | if (json_get_number(weather, "min_temp", &wi->min_temp) < 0) 268 | goto out0; 269 | if (json_get_string(weather, "condition", &wi->condition) < 0) 270 | goto out0; 271 | if (json_get_string(weather, "provider", &wi->provider) < 0) 272 | goto out0; 273 | if (json_get_string(weather, "location", &wi->location) < 0) 274 | goto out0; 275 | 276 | forecast = cJSON_GetObjectItemCaseSensitive(weather, "forecast"); 277 | if (!forecast || !cJSON_IsArray(forecast)) 278 | log_err_to(out0, "'forecast' array not found!\n"); 279 | 280 | i = 0; 281 | cJSON_ArrayForEach(day, forecast) { 282 | if (i >= 3) 283 | break; 284 | if (json_get_number(day, "max_temp", &wi->forecast[i].max_temp) < 0) 285 | goto out0; 286 | if (json_get_number(day, "min_temp", &wi->forecast[i].min_temp) < 0) 287 | goto out0; 288 | if (json_get_string(day, "condition", &wi->forecast[i].condition) < 0) 289 | goto out0; 290 | i++; 291 | } 292 | 293 | /* Check if all fields were filled. */ 294 | if (i != 3) 295 | log_err_to(out0, "'forecast' array have missing items (%d/3)!\n", i); 296 | 297 | /* Validate all weather conditions. */ 298 | if (!is_condition_valid(wi->condition)) 299 | goto out0; 300 | 301 | for (i = 0; i < 3; i++) 302 | if (!is_condition_valid(wi->forecast[i].condition)) 303 | goto out0; 304 | 305 | cJSON_Delete(weather); 306 | return (0); 307 | out0: 308 | weather_free(wi); 309 | cJSON_Delete(weather); 310 | return (-1); 311 | } 312 | 313 | /** 314 | * @brief Deallocates the current data saved into the 315 | * weather_info structure. 316 | * 317 | * @param wi Weather info structure. 318 | */ 319 | void weather_free(struct weather_info *wi) 320 | { 321 | if (!wi) 322 | return; 323 | free(wi->location); 324 | free(wi->provider); 325 | free(wi->condition); 326 | for (int i = 0; i < 3; i++) 327 | free(wi->forecast[i].condition); 328 | } 329 | 330 | /** 331 | * @brief Issues the command provided by the user, reads its 332 | * stdout and parses its json. 333 | * 334 | * @param command Command to be issued. 335 | * @param wi Weather info structure to be filled. 336 | * 337 | * @return Returns 0 if success, -1 otherwise. 338 | */ 339 | int weather_get(const char *command, struct weather_info *wi) 340 | { 341 | int ret; 342 | FILE *f; 343 | struct abuf ab; 344 | char tmp[256] = {0}; 345 | 346 | ret = -1; 347 | 348 | if (abuf_alloc(&ab) < 0) 349 | return (ret); 350 | 351 | f = popen(command, "r"); 352 | if (!f) 353 | goto out0; 354 | 355 | while (fgets(tmp, sizeof(tmp), f)) 356 | abuf_append(&ab, tmp, strlen(tmp)); 357 | 358 | weather_free(wi); 359 | if (json_parse_weather(ab.str, wi) < 0) 360 | goto out1; 361 | 362 | ret = 0; 363 | out1: 364 | pclose(f); 365 | out0: 366 | abuf_free(&ab); 367 | return (ret); 368 | } 369 | 370 | /** 371 | * @brief Checks if the current hour is day or not. 372 | * 373 | * @return Returns 1 if day, 0 if night. 374 | */ 375 | int weather_is_day(void) 376 | { 377 | time_t now; 378 | struct tm *now_tm; 379 | now = time(NULL); 380 | now_tm = localtime(&now); 381 | return (now_tm->tm_hour >= 06 && now_tm->tm_hour <= 17); 382 | } 383 | 384 | /** 385 | * @brief Returns the days number for the next 386 | * three days. 387 | * 388 | * @param d1 Next day 1. 389 | * @param d2 Next day 2. 390 | * @param d3 Next day 3. 391 | */ 392 | void weather_get_forecast_days(int *d1, int *d2, int *d3) 393 | { 394 | time_t now; 395 | struct tm *now_tm; 396 | now = time(NULL); 397 | now_tm = localtime(&now); 398 | *d1 = (now_tm->tm_wday + 1) % 7; 399 | *d2 = (now_tm->tm_wday + 2) % 7; 400 | *d3 = (now_tm->tm_wday + 3) % 7; 401 | } 402 | 403 | /** 404 | * @brief Roughly calculates the current moon phase 405 | * and returns an string pointing the path of the 406 | * corresponding moon asset. 407 | * 408 | * Note: Algorithm based on this one: 409 | * https://minkukel.com/en/various/calculating-moon-phase/ 410 | * but adapted to my needs, i.e., using only for 4 411 | * main phases, instead of the 8. 412 | * 413 | * @return Returns an string pointing the current 414 | * moon phase icon. 415 | */ 416 | const char *weather_get_moon_phase_icon(void) 417 | { 418 | time_t now, first_new_moon; 419 | int currentphase; 420 | double lunarsecs; 421 | double totalsecs; 422 | double currentsecs; 423 | double currentfrac; 424 | 425 | now = time(NULL); 426 | 427 | /* 428 | * Epoch of the first new moon on 2000s: 429 | * 2000-01-06 18:14 UTC. 430 | */ 431 | first_new_moon = 947182440; 432 | 433 | /* Calculate moon phase. */ 434 | lunarsecs = LUNAR_CYCLE_CONSTANT * 86400.0; 435 | totalsecs = difftime(now, first_new_moon); 436 | currentsecs = fmod(totalsecs, lunarsecs); 437 | currentfrac = currentsecs / lunarsecs; 438 | currentphase = (int)round(currentfrac * 4) % 4; 439 | return (moon_phases[currentphase]); 440 | } 441 | -------------------------------------------------------------------------------- /weather.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Davidson Francis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef WEATHER_H 26 | #define WEATHER_H 27 | 28 | struct weather_info 29 | { 30 | int temperature; 31 | int max_temp; 32 | int min_temp; 33 | char *condition; 34 | char *location; 35 | char *provider; 36 | struct forecast 37 | { 38 | int max_temp; 39 | int min_temp; 40 | char *condition; 41 | } forecast[3]; 42 | }; 43 | 44 | extern void weather_free(struct weather_info *wi); 45 | extern int weather_get(const char *command, 46 | struct weather_info *wi); 47 | extern int weather_is_day(void); 48 | extern void weather_get_forecast_days(int *d1, int *d2, int *d3); 49 | extern const char *weather_get_moon_phase_icon(void); 50 | 51 | #endif /* WEATHER_H */ 52 | --------------------------------------------------------------------------------