diff --git a/CMakeLists.txt b/CMakeLists.txt index 533340744..99db5c69a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -235,6 +235,7 @@ set(LIBFASTFETCH_SRC src/modules/vulkan.c src/modules/localip.c src/modules/publicip.c + src/modules/weather.c src/modules/player.c src/modules/song.c src/modules/datetime.c @@ -333,6 +334,8 @@ add_library(libfastfetch OBJECT ${LIBFASTFETCH_SRC} ) +target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE) + CHECK_INCLUDE_FILE("sys/sysinfo.h" HAVE_SYSINFO_H) if(HAVE_SYSINFO_H) # needs to be public, because changes fastfech.h ABI diff --git a/completions/bash b/completions/bash index 5c645d898..2a3822ea6 100644 --- a/completions/bash +++ b/completions/bash @@ -217,7 +217,10 @@ __fastfetch_completion() "--set" "--set-keyless" "--player-name" + "--public-ip-url" "--public-ip-timeout" + "--weather-output-format" + "--weather-timeout" "--os-key" "--os-format" "--os-error" @@ -302,6 +305,9 @@ __fastfetch_completion() "--public-ip-key" "--public-ip-format" "--public-ip-error" + "--weather-key" + "--weather-format" + "--weather-error" "--player-key" "--player-format" "--player-error" diff --git a/presets/all b/presets/all index 6bd7c776d..a1706f3b1 100644 --- a/presets/all +++ b/presets/all @@ -1 +1 @@ ---structure Title:Separator:OS:Host:Kernel:Uptime:Processes:Packages:Shell:Resolution:DE:WM:WMTheme:Theme:Icons:Font:Cursor:Terminal:TerminalFont:CPU:GPU:Memory:Swap:Disk:Battery:PowerAdapter:Player:Song:PublicIP:LocalIP:DateTime:Locale:Vulkan:OpenGL:OpenCL:Users:Break:Colors +--structure Title:Separator:OS:Host:Kernel:Uptime:Processes:Packages:Shell:Resolution:DE:WM:WMTheme:Theme:Icons:Font:Cursor:Terminal:TerminalFont:CPU:GPU:Memory:Swap:Disk:Battery:PowerAdapter:Player:Song:PublicIP:LocalIP:DateTime:Locale:Vulkan:OpenGL:OpenCL:Users:Weather:Break:Colors diff --git a/src/common/init.c b/src/common/init.c index 53fe6467a..0c4674755 100644 --- a/src/common/init.c +++ b/src/common/init.c @@ -188,6 +188,7 @@ static void defaultConfig(FFinstance* instance) initModuleArg(&instance->config.locale); initModuleArg(&instance->config.localIP); initModuleArg(&instance->config.publicIP); + initModuleArg(&instance->config.weather); initModuleArg(&instance->config.player); initModuleArg(&instance->config.song); initModuleArg(&instance->config.dateTime); @@ -243,6 +244,10 @@ static void defaultConfig(FFinstance* instance) ffStrbufInit(&instance->config.localIpNamePrefix); instance->config.publicIpTimeout = 0; + ffStrbufInit(&instance->config.publicIpUrl); + + instance->config.weatherTimeout = 0; + ffStrbufInitS(&instance->config.weatherOutputFormat, "%t+-+%C+(%l)"); ffStrbufInitA(&instance->config.osFile, 0); @@ -358,8 +363,6 @@ static void exitSignalHandler(int signal) void ffStart(FFinstance* instance) { - ffPrepareCPUUsage(); - if(instance->config.multithreading) startDetectionThreads(instance); diff --git a/src/common/networking.c b/src/common/networking.c index fa8be0acc..92d1b925c 100644 --- a/src/common/networking.c +++ b/src/common/networking.c @@ -6,22 +6,23 @@ #include #include -void ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, FFstrbuf* buffer) +int ffNetworkingSendHttpRequest(const char* host, const char* path, const char* headers, uint32_t timeout) { - struct addrinfo hints = {0}; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; + struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }; struct addrinfo* addr; if(getaddrinfo(host, "80", &hints, &addr) != 0) - return; + return -1; - int sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); - if(sock == -1) + int sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if(sockfd == -1) { freeaddrinfo(addr); - return; + return -1; } if(timeout > 0) @@ -29,14 +30,14 @@ void ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, F struct timeval timev; timev.tv_sec = 0; timev.tv_usec = (__typeof__(timev.tv_usec)) (timeout * 1000); //milliseconds to microseconds - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev)); + setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev)); } - if(connect(sock, addr->ai_addr, addr->ai_addrlen) == -1) + if(connect(sockfd, addr->ai_addr, addr->ai_addrlen) == -1) { - close(sock); + close(sockfd); freeaddrinfo(addr); - return; + return -1; } freeaddrinfo(addr); @@ -47,16 +48,23 @@ void ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, F ffStrbufAppendS(&command, path); ffStrbufAppendS(&command, " HTTP/1.1\nHost: "); ffStrbufAppendS(&command, host); - ffStrbufAppendS(&command, "\r\n\r\n"); + ffStrbufAppendS(&command, "\r\n"); + ffStrbufAppendS(&command, headers); + ffStrbufAppendS(&command, "\r\n"); - if(send(sock, command.chars, command.length, 0) == -1) + if(send(sockfd, command.chars, command.length, 0) == -1) { ffStrbufDestroy(&command); - close(sock); - return; + close(sockfd); + return -1; } + ffStrbufDestroy(&command); + return sockfd; +} - ssize_t received = recv(sock, buffer->chars + buffer->length, ffStrbufGetFree(buffer), 0); +void ffNetworkingRecvHttpResponse(int sockfd, FFstrbuf* buffer) +{ + ssize_t received = recv(sockfd, buffer->chars + buffer->length, ffStrbufGetFree(buffer), 0); if(received > 0) { @@ -64,6 +72,12 @@ void ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, F buffer->chars[buffer->length] = '\0'; } - ffStrbufDestroy(&command); - close(sock); + close(sockfd); +} + +void ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, const char* headers, FFstrbuf* buffer) +{ + int sockfd = ffNetworkingSendHttpRequest(host, path, headers, timeout); + if(sockfd > 0) + ffNetworkingRecvHttpResponse(sockfd, buffer); } diff --git a/src/common/networking.h b/src/common/networking.h index 7f239cf84..cab005902 100644 --- a/src/common/networking.h +++ b/src/common/networking.h @@ -5,6 +5,8 @@ #include "util/FFstrbuf.h" -void ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, FFstrbuf* buffer); +int ffNetworkingSendHttpRequest(const char* host, const char* path, const char* headers, uint32_t timeout); +void ffNetworkingRecvHttpResponse(int sock, FFstrbuf* buffer); +void ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, const char* headers, FFstrbuf* buffer); #endif diff --git a/src/data/config_user.txt b/src/data/config_user.txt index d745e0ba6..c0ddbcf73 100644 --- a/src/data/config_user.txt +++ b/src/data/config_user.txt @@ -151,12 +151,30 @@ # Default is "-" #--separator-string - +# Public IP URL option: +# Sets the URL of public IP detection server to be used. +# Only HTTP protocol is supported, and the value should not contain "http://" prefix. +# Default is "ipinfo.io/ip". +#--public-ip-url "ipinfo.io/ip" + # Public IP timeout option: # Sets the time to wait for the public ip server to respond. # Must be a positive integer. # Default is 0 (disabled). #--public-ip-timeout 0 +# Weather output format option: +# Sets the weather format to be used. It must be URI encoded. +# See: https://github.com/chubin/wttr.in#one-line-output +# Default is "%t+-+%C+(%l)". +#--weather-output-format "%t+-+%C+(%l)" + +# Weather timeout option: +# Sets the time to wait for the weather server (wttr.in) to respond. +# Must be a positive integer. +# Default is 0 (disabled). +#--weather-timeout 0 + # OS file option # Sets the path to the file containing the operating system information. # Should be a valid path to an existing file. @@ -213,6 +231,7 @@ #--locale-key Locale #--local-ip-key Local IP ({1}) #--public-ip-key Public IP +#--weather-key Weather #--player-key Media Player #--song-key Song #--datetime-key Date Time @@ -254,6 +273,7 @@ #--locale-format #--local-ip-format #--public-ip-format +#--weather-format #--player-format #--song-format #--datetime-format @@ -295,6 +315,7 @@ #--locale-error #--local-ip-error #--public-ip-error +#--weather-error #--player-error #--song-error #--datetime-error diff --git a/src/data/help.txt b/src/data/help.txt index 59bf40e31..c5b2c0076 100644 --- a/src/data/help.txt +++ b/src/data/help.txt @@ -103,6 +103,9 @@ Module specific options: --localip-show-loop : Show loop back addresses (127.0.0.1) in local ip module. Default is false --localip-name-prefix : Show ips with given name prefix only. Default is empty --public-ip-timeout: Time in milliseconds to wait for the public ip server to respond. Default is disabled (0) + --public-ip-url: The URL of public IP detection server to be used. + --weather-timeout: Time in milliseconds to wait for the weather server to respond. Default is disabled (0) + --weather-output-format: The output weather format to be used. It must be URI encoded. --player-name: The name of the player to use --gl : Sets the opengl context creation library to use. Must be auto, egl, glx or osmesa. Default is auto diff --git a/src/detection/displayserver/linux/wayland.c b/src/detection/displayserver/linux/wayland.c index 03d3f907f..39743282c 100644 --- a/src/detection/displayserver/linux/wayland.c +++ b/src/detection/displayserver/linux/wayland.c @@ -1,5 +1,3 @@ -#define _GNU_SOURCE //required for struct ucred - #include "displayserver_linux.h" #include diff --git a/src/fastfetch.c b/src/fastfetch.c index 9dc7cdef0..a42c0d802 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -1151,6 +1151,12 @@ static void parseOption(FFinstance* instance, FFdata* data, const char* key, con optionParseString(key, value, &instance->config.publicIP.outputFormat); else if(strcasecmp(key, "--public-ip-error") == 0) optionParseString(key, value, &instance->config.publicIP.errorFormat); + else if(strcasecmp(key, "--weather-key") == 0) + optionParseString(key, value, &instance->config.weather.key); + else if(strcasecmp(key, "--weather-format") == 0) + optionParseString(key, value, &instance->config.weather.outputFormat); + else if(strcasecmp(key, "--weather-error") == 0) + optionParseString(key, value, &instance->config.weather.errorFormat); else if(strcasecmp(key, "--player-key") == 0) optionParseString(key, value, &instance->config.player.key); else if(strcasecmp(key, "--player-format") == 0) @@ -1291,8 +1297,14 @@ static void parseOption(FFinstance* instance, FFdata* data, const char* key, con optionParseString(key, value, &instance->config.osFile); else if(strcasecmp(key, "--player-name") == 0) optionParseString(key, value, &instance->config.playerName); + else if(strcasecmp(key, "--public-ip-url") == 0) + optionParseString(key, value, &instance->config.publicIpUrl); else if(strcasecmp(key, "--public-ip-timeout") == 0) instance->config.publicIpTimeout = optionParseUInt32(key, value); + else if(strcasecmp(key, "--weather-output-format") == 0) + optionParseString(key, value, &instance->config.weatherOutputFormat); + else if(strcasecmp(key, "--weather-timeout") == 0) + instance->config.weatherTimeout = optionParseUInt32(key, value); else if(strcasecmp(key, "--gl") == 0) { optionParseEnum(key, value, &instance->config.glType, @@ -1428,6 +1440,8 @@ static void parseStructureCommand(FFinstance* instance, FFdata* data, const char ffPrintLocalIp(instance); else if(strcasecmp(line, "publicip") == 0) ffPrintPublicIp(instance); + else if(strcasecmp(line, "weather") == 0) + ffPrintWeather(instance); else if(strcasecmp(line, "player") == 0) ffPrintPlayer(instance); else if(strcasecmp(line, "media") == 0 || strcasecmp(line, "song") == 0) @@ -1471,6 +1485,19 @@ int main(int argc, const char** argv) if(data.structure.length == 0) ffStrbufAppendS(&data.structure, FASTFETCH_DATATEXT_STRUCTURE); + #define FF_CONTAINS_MODULE_NAME(moduleName)\ + ffStrbufContainIgnCaseS(&data.structure, ":" #moduleName ":") ||\ + ffStrbufStartsWithIgnCaseS(&data.structure, #moduleName ":") ||\ + ffStrbufEndsWithIgnCaseS(&data.structure, ":" #moduleName) + + if(FF_CONTAINS_MODULE_NAME(CPUUsage)) + ffPrepareCPUUsage(); + if(FF_CONTAINS_MODULE_NAME(PublicIp)) + ffPreparePublicIp(&instance); + if(FF_CONTAINS_MODULE_NAME(Weather)) + ffPrepareWeather(&instance); + #undef FF_CONTAINS_MODULE_NAME + ffStart(&instance); //Parse the structure and call the modules diff --git a/src/fastfetch.h b/src/fastfetch.h index b4ea8b055..085d9b82e 100644 --- a/src/fastfetch.h +++ b/src/fastfetch.h @@ -120,6 +120,7 @@ typedef struct FFconfig FFModuleArgs locale; FFModuleArgs localIP; FFModuleArgs publicIP; + FFModuleArgs weather; FFModuleArgs player; FFModuleArgs song; FFModuleArgs dateTime; @@ -174,8 +175,12 @@ typedef struct FFconfig bool localIpShowIpV6; FFstrbuf localIpNamePrefix; + FFstrbuf publicIpUrl; uint32_t publicIpTimeout; + FFstrbuf weatherOutputFormat; + uint32_t weatherTimeout; + FFstrbuf osFile; FFstrbuf playerName; @@ -236,6 +241,8 @@ void ffLogoBuiltinListAutocompletion(); void ffPrintDateTimeFormat(FFinstance* instance, const char* moduleName, const FFModuleArgs* moduleArgs); void ffPrepareCPUUsage(); +void ffPreparePublicIp(FFinstance* instance); +void ffPrepareWeather(FFinstance* instance); //Printing @@ -276,6 +283,7 @@ void ffPrintDate(FFinstance* instance); void ffPrintTime(FFinstance* instance); void ffPrintLocalIp(FFinstance* instance); void ffPrintPublicIp(FFinstance* instance); +void ffPrintWeather(FFinstance* instance); void ffPrintColors(FFinstance* instance); void ffPrintVulkan(FFinstance* instance); void ffPrintOpenGL(FFinstance* instance); diff --git a/src/flashfetch.c b/src/flashfetch.c index 15609669c..e20b2aa1b 100644 --- a/src/flashfetch.c +++ b/src/flashfetch.c @@ -10,6 +10,10 @@ int main(int argc, char** argv) //Modify instance.config here + // ffPrepareCPUUsage(); + // ffPreparePublicIp(&instance); + // ffPrepareWeather(&instance); + //Does things like starting detection threads, disabling line wrap, etc ffStart(&instance); @@ -53,6 +57,7 @@ int main(int argc, char** argv) //ffPrintOpenGL(&instance); //ffPrintOpenCL(&instance); //ffPrintUsers(&instance); + //ffPrintWeather(&instance); ffPrintBreak(&instance); ffPrintColors(&instance); diff --git a/src/modules/publicip.c b/src/modules/publicip.c index 4ff5e59b4..513ccb60a 100644 --- a/src/modules/publicip.c +++ b/src/modules/publicip.c @@ -5,16 +5,54 @@ #define FF_PUBLICIP_MODULE_NAME "Public IP" #define FF_PUBLICIP_NUM_FORMAT_ARGS 1 +static int sockfd; + +void ffPreparePublicIp(FFinstance* instance) +{ + if(instance->config.publicIpUrl.length == 0) + sockfd = ffNetworkingSendHttpRequest("ipinfo.io", "/ip", NULL, instance->config.publicIpTimeout); + else + { + FFstrbuf host; + ffStrbufInitCopy(&host, &instance->config.publicIpUrl); + ffStrbufSubstrAfterFirstS(&host, "://"); + uint32_t pathStartIndex = ffStrbufFirstIndexC(&host, '/'); + + FFstrbuf path; + ffStrbufInit(&path); + if(pathStartIndex != host.length) + { + ffStrbufAppendNS(&path, pathStartIndex, host.chars + (host.length - pathStartIndex)); + host.length = pathStartIndex; + host.chars[pathStartIndex] = '\0'; + } + + sockfd = ffNetworkingSendHttpRequest(host.chars, path.length == 0 ? "/" : path.chars, NULL, instance->config.publicIpTimeout); + + ffStrbufDestroy(&path); + ffStrbufDestroy(&host); + } +} + void ffPrintPublicIp(FFinstance* instance) { + if(sockfd == 0) + ffPreparePublicIp(instance); + + if(sockfd < 0) + { + ffPrintError(instance, FF_PUBLICIP_MODULE_NAME, 0, &instance->config.publicIP, "Failed to connect to an IP detection server"); + return; + } + FFstrbuf result; ffStrbufInitA(&result, 4096); - ffNetworkingGetHttp("ipinfo.io", "/ip", instance->config.publicIpTimeout, &result); + ffNetworkingRecvHttpResponse(sockfd, &result); ffStrbufSubstrAfterFirstS(&result, "\r\n\r\n"); if(result.length == 0) { - ffPrintError(instance, FF_PUBLICIP_MODULE_NAME, 0, &instance->config.publicIP, "Failed to connect to an IP detection server"); + ffPrintError(instance, FF_PUBLICIP_MODULE_NAME, 0, &instance->config.publicIP, "Failed to receive the server response"); ffStrbufDestroy(&result); return; } diff --git a/src/modules/weather.c b/src/modules/weather.c new file mode 100644 index 000000000..c85b429cb --- /dev/null +++ b/src/modules/weather.c @@ -0,0 +1,55 @@ +#include "fastfetch.h" +#include "common/printing.h" +#include "common/networking.h" + +#define FF_WEATHER_MODULE_NAME "Weather" +#define FF_WEATHER_NUM_FORMAT_ARGS 1 + +static int sockfd; + +void ffPrepareWeather(FFinstance* instance) +{ + FFstrbuf path; + ffStrbufInitS(&path, "/?format="); + ffStrbufAppend(&path, &instance->config.weatherOutputFormat); + sockfd = ffNetworkingSendHttpRequest("wttr.in", path.chars, "User-Agent: curl/0.0.0\r\n", instance->config.weatherTimeout); + ffStrbufDestroy(&path); +} + +void ffPrintWeather(FFinstance* instance) +{ + if(sockfd == 0) + ffPrepareWeather(instance); + + if(sockfd < 0) + { + ffPrintError(instance, FF_WEATHER_MODULE_NAME, 0, &instance->config.weather, "Failed to connect to 'wttr.in'"); + return; + } + + FFstrbuf result; + ffStrbufInitA(&result, 4096); + ffNetworkingRecvHttpResponse(sockfd, &result); + ffStrbufSubstrAfterFirstS(&result, "\r\n\r\n"); + + if(result.length == 0) + { + ffPrintError(instance, FF_WEATHER_MODULE_NAME, 0, &instance->config.weather, "Failed to receive the server response"); + ffStrbufDestroy(&result); + return; + } + + if(instance->config.weather.outputFormat.length == 0) + { + ffPrintLogoAndKey(instance, FF_WEATHER_MODULE_NAME, 0, &instance->config.weather.key); + ffStrbufPutTo(&result, stdout); + } + else + { + ffPrintFormat(instance, FF_WEATHER_MODULE_NAME, 0, &instance->config.weather, FF_WEATHER_NUM_FORMAT_ARGS, (FFformatarg[]) { + {FF_FORMAT_ARG_TYPE_STRBUF, &result} + }); + } + + ffStrbufDestroy(&result); +} diff --git a/src/util/FFstrbuf.h b/src/util/FFstrbuf.h index ec985b84f..d6397c6dc 100644 --- a/src/util/FFstrbuf.h +++ b/src/util/FFstrbuf.h @@ -159,7 +159,7 @@ static inline FF_C_NODISCARD int ffStrbufIgnCaseComp(const FFstrbuf* strbuf, con static inline FF_C_NODISCARD bool ffStrbufContainS(const FFstrbuf* strbuf, const char* str) { - return strnstr(strbuf->chars, str, strbuf->length) != NULL; + return strstr(strbuf->chars, str) != NULL; } static inline FF_C_NODISCARD bool ffStrbufContainIgnCaseS(const FFstrbuf* strbuf, const char* str)