Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to display webpage titles by right clicking in chat #13724

Closed
wants to merge 10 commits into from
2 changes: 2 additions & 0 deletions builtin/settingtypes.txt
Expand Up @@ -714,6 +714,8 @@ chat_weblink_color (Weblink color) string #8888FF
# Value 0 will use the default font size.
chat_font_size (Chat font size) int 0 0 72

# Right Click links to show webpage title
right_click_chat_web_titles (Rightclick webpage title) bool false

[**Content Repository]

Expand Down
1 change: 1 addition & 0 deletions src/defaultsettings.cpp
Expand Up @@ -68,6 +68,7 @@ void set_default_settings()
settings->setDefault("occlusion_culler", "bfs");
settings->setDefault("enable_raytraced_culling", "true");
settings->setDefault("chat_weblink_color", "#8888FF");
settings->setDefault("right_click_chat_web_titles","false");

// Keymap
settings->setDefault("remote_port", "30000");
Expand Down
90 changes: 76 additions & 14 deletions src/gui/guiChatConsole.cpp
Expand Up @@ -33,6 +33,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/string.h"
#include <string>

#ifdef USE_CURL //specific to the webpage title feature
#include "httpfetch.h"
#include <thread> // we only need threads when curl is there (as getting a webpage title could freeze the entire client otherwise)
#include <regex>
#endif

inline u32 clamp_u8(s32 value)
{
return (u32) MYMIN(MYMAX(value, 0), 255);
Expand Down Expand Up @@ -96,6 +102,7 @@ GUIChatConsole::GUIChatConsole(
// track ctrl keys for mouse event
m_is_ctrl_down = false;
m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks");
m_cache_right_click_chat_web_titles = g_settings->getBool("right_click_chat_web_titles");
}

GUIChatConsole::~GUIChatConsole()
Expand Down Expand Up @@ -673,7 +680,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
{
// Translate pixel position to font position
bool was_url_pressed = m_cache_clickable_chat_weblinks &&
weblinkClick(event.MouseInput.X / m_fontsize.X,
weblinkClickOpen(event.MouseInput.X / m_fontsize.X,
event.MouseInput.Y / m_fontsize.Y);

if (!was_url_pressed
Expand All @@ -690,6 +697,14 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
}
}
}
else if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN && m_cache_right_click_chat_web_titles){
if (event.MouseInput.Y / m_fontsize.Y < (m_height / m_fontsize.Y) - 1 ){
std::thread t(&GUIChatConsole::weblinkClickTitle, this, getLinkAt(event.MouseInput.X / m_fontsize.X,
event.MouseInput.Y / m_fontsize.Y),
m_chat_backend);
t.detach();
}
}
}
else if(event.EventType == EET_STRING_INPUT_EVENT)
{
Expand All @@ -710,35 +725,45 @@ void GUIChatConsole::setVisible(bool visible)
}
}

bool GUIChatConsole::weblinkClick(s32 col, s32 row)
{
// Prevent accidental rapid clicking
static u64 s_oldtime = 0;
u64 newtime = porting::getTimeMs();

// 0.6 seconds should suffice
if (newtime - s_oldtime < 600)
return false;
s_oldtime = newtime;

std::string GUIChatConsole::getLinkAt(s32 col, s32 row){
chmodsayshello marked this conversation as resolved.
Show resolved Hide resolved
const std::vector<ChatFormattedFragment> &
frags = m_chat_backend->getConsoleBuffer().getFormattedLine(row).fragments;
frags = m_chat_backend->getConsoleBuffer().getFormattedLine(row).fragments;
std::string weblink = ""; // from frag meta

// Identify targetted fragment, if exists
int indx = frags.size() - 1;
if (indx < 0) {
// Invalid row, frags is empty
return false;
return "";
}
// Scan from right to left, offset by 1 font space because left margin
while (indx > -1 && (u32)col < frags[indx].column + 1) {
--indx;
}
if (indx > -1) {
weblink = frags[indx].weblink;
return weblink;
// Note if(indx < 0) then a frag somehow had a corrupt column field
}
return "";
}

bool GUIChatConsole::weblinkClickOpen(s32 col, s32 row)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that this PR does not add this code, all it did to it was changing it's name (which should be fine as it's private), and separate a part of it, as the feature added requires that as well. That somehow confused the git diff

{
// Prevent accidental rapid clicking
static u64 s_oldtime = 0;
u64 newtime = porting::getTimeMs();

// 0.6 seconds should suffice
if (newtime - s_oldtime < 600)
return false;
s_oldtime = newtime;

std::string weblink = getLinkAt(col, row);

if (weblink.empty()){
return false;
}

/*
// Debug help. Please keep this in case adjustments are made later.
Expand Down Expand Up @@ -773,6 +798,43 @@ bool GUIChatConsole::weblinkClick(s32 col, s32 row)
return false;
}

void GUIChatConsole::weblinkClickTitle(const std::string &weblink, ChatBackend* chat_backend){
if (weblink.empty()){
return;
}

std::ostringstream msg;
msg << " * ";

#ifdef USE_CURL
const u64 caller = httpfetch_caller_alloc_secure();
HTTPFetchResult fetch_result;
HTTPFetchRequest fetch_request;
fetch_request.url = weblink;
fetch_request.caller = caller;
fetch_request.max_file_size = 1.5 * 1024 * 1024; // 1.5 megabyte should be more than enoght for HTML!
fetch_request.extra_headers = {"Accept: text/html"};

httpfetch_sync(fetch_request, fetch_result); // sync because this is not the main thread

httpfetch_caller_free(caller);

if (fetch_result.succeeded) {
const std::regex r("<title.*?>(.*?)</title.*?>");
std::smatch matches;
if (std::regex_search(fetch_result.data, matches, r) || !matches.empty()) {
const std::string title = matches.str(1); // pick the first one, what if this is a webpage teaching html and explain the title tag in the CONTENT section...
msg << gettext("Webpage title:") << " '" << title << "'";
} else {
msg << gettext("Unable to get the title from webpage!"); // Either the HTML document doesn't have a title tag OR the server reponded with another format
}
} // No need to print some text of it fails here, httpfetch already does that
#else
msg << gettext("Unable to connect to webpage (cURL missing)!");
#endif
chat_backend->addUnparsedMessage(utf8_to_wide(msg.str()));

}
void GUIChatConsole::updatePrimarySelection()
{
#if IRRLICHT_VERSION_MT_REVISION >= 11
Expand Down
10 changes: 9 additions & 1 deletion src/gui/guiChatConsole.h
Expand Up @@ -84,9 +84,15 @@ class GUIChatConsole : public gui::IGUIElement
void drawText();
void drawPrompt();

// If there is a weblink at the specified place, it gets returned. Empty string otherwise
std::string getLinkAt(s32 col, s32 row);

// If clicked fragment has a web url, send it to the system default web browser.
// Returns true if, and only if a web url was pressed.
bool weblinkClick(s32 col, s32 row);
bool weblinkClickOpen(s32 col, s32 row);

// If a weblink was clicked, this will get the page's title and display it in chat
void weblinkClickTitle(const std::string &weblink, ChatBackend* chat_backen);

// If the selected text changed, we need to update the (X11) primary selection.
void updatePrimarySelection();
Expand Down Expand Up @@ -136,6 +142,8 @@ class GUIChatConsole : public gui::IGUIElement

// Enable clickable chat weblinks
bool m_cache_clickable_chat_weblinks;
// Enable right clickable chat website titles
bool m_cache_right_click_chat_web_titles;
// Track if a ctrl key is currently held down
bool m_is_ctrl_down;
};
1 change: 1 addition & 0 deletions src/httpfetch.cpp
Expand Up @@ -275,6 +275,7 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_,
request.timeout);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS,
request.connect_timeout);
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE , request.max_file_size);

if (!request.useragent.empty())
curl_easy_setopt(curl, CURLOPT_USERAGENT, request.useragent.c_str());
Expand Down
3 changes: 3 additions & 0 deletions src/httpfetch.h
Expand Up @@ -66,6 +66,9 @@ struct HTTPFetchRequest
// application/x-www-form-urlencoded. POST-only.
bool multipart = false;

// The maximum file size in bytes. Allow any size by default
long max_file_size = -1;

// The Method to use default = GET
// Avaible methods GET, POST, PUT, DELETE
HttpMethod method = HTTP_GET;
Expand Down