$ git clone https://tcclient.ion.nu/tc_client.git
commit 3b28271ef7910cf382fb498094c8ec636a9fb065
Author: Alicia <...>
Date: Thu Sep 29 02:23:11 2016 +0200
tc_client-gtk: reimplemented automatic scrolling using a state to keep track of whether the GtkTextView is scrolled to the bottom and using gtk_text_view_scroll_to_mark() to scroll.
diff --git a/ChangeLog b/ChangeLog
index ce0fbe7..7e3ab65 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -9,6 +9,7 @@ tc_client-gtk: use the camera's own copy of its ID for g_timeout_add() rather th
tc_client-gtk: cleaned up leftover windows compatibility code that is no longer necessary.
tc_client-gtk: changed the greenscreen postprocessor to use libcamera for the background, allowing you to use either another camera or an image through the virtual "Image" camera as background.
tc_client-gtk: turn URLs mentioned in chat into clickable links.
+tc_client-gtk: reimplemented automatic scrolling using a state to keep track of whether the GtkTextView is scrolled to the bottom and using gtk_text_view_scroll_to_mark() to scroll.
tc_client-gtk and camviewer: updated to libavcodec's avcodec_{send,receive}_{frame,packet} API.
libcamera(v4l2): cache the frame and if there is no data to be read, return the cache instead of blocking.
0.39:
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index 8e4581f..eaa5776 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -69,8 +69,6 @@ struct viddata
int audiopipe;
SwrContext* swrctx;
#endif
- GtkTextBuffer* buffer;
- GtkAdjustment* scroll;
};
struct viddata* data;
@@ -80,6 +78,7 @@ const char* channel=0;
const char* mycolor=0;
char* nickname=0;
char frombuild=0; // Running from the build directory
+struct chatview* mainchat;
#define TC_CLIENT (frombuild?"./tc_client":"tc_client")
#ifdef _WIN32
PROCESS_INFORMATION coreprocess={.hProcess=0};
@@ -87,19 +86,16 @@ char frombuild=0; // Running from the build directory
void printchat(const char* text, const char* color, unsigned int offset, const char* pm)
{
- GtkAdjustment* scroll;
- GtkTextBuffer* buffer;
+ struct chatview* chatview;
struct user* user;
if(pm && (user=finduser(pm)))
{
- pm_open(pm, 0, data->scroll);
- scroll=user->pm_scroll;
- buffer=user->pm_buffer;
+ pm_open(pm, 0);
+ chatview=user->pm_chatview;
}else{
- scroll=data->scroll;
- buffer=data->buffer;
+ chatview=mainchat;
}
- char bottom=autoscroll_before(scroll);
+ GtkTextBuffer* buffer=gtk_text_view_get_buffer(chatview->textview);
// Insert new content
GtkTextIter end;
gtk_text_buffer_get_end_iter(buffer, &end);
@@ -146,7 +142,7 @@ void printchat(const char* text, const char* color, unsigned int offset, const c
gtk_text_buffer_apply_tag_by_name(buffer, "nickname", &start, &end);
}
buffer_updatesize(buffer);
- if(bottom){autoscroll_after(scroll);}
+ chatview_autoscroll(chatview);
}
unsigned int cameventsource=0;
@@ -615,24 +611,36 @@ void togglecam(GtkCheckMenuItem* item, struct viddata* data)
gboolean handleresize(GtkWidget* widget, GdkEventConfigure* event, struct viddata* data)
{
- char bottom=autoscroll_before(data->scroll);
if(event->width!=gtk_widget_get_allocated_width(cambox))
{
updatescaling(event->width, 0, 0);
}
-#ifndef _WIN32 // For some reason scrolling as a response to resizing freezes windows
- if(bottom){autoscroll_after(data->scroll);}
-#endif
+ // Fix scrolling
+ chatview_autoscroll(mainchat);
+ unsigned int i;
+ for(i=0; i<usercount; ++i)
+ {
+ if(userlist[i].pm_chatview)
+ {
+ chatview_autoscroll(userlist[i].pm_chatview);
+ }
+ }
return 0;
}
void handleresizepane(GObject* obj, GParamSpec* spec, struct viddata* data)
{
- char bottom=autoscroll_before(data->scroll);
updatescaling(0, gtk_paned_get_position(GTK_PANED(obj)), 0);
-#ifndef _WIN32
- if(bottom){autoscroll_after(data->scroll);}
-#endif
+ // Fix scrolling
+ chatview_autoscroll(mainchat);
+ unsigned int i;
+ for(i=0; i<usercount; ++i)
+ {
+ if(userlist[i].pm_chatview)
+ {
+ chatview_autoscroll(userlist[i].pm_chatview);
+ }
+ }
}
gboolean inputkeys(GtkWidget* widget, GdkEventKey* event, void* data)
@@ -702,7 +710,7 @@ void sendmessage(GtkEntry* entry, void* x)
char* pm=0;
if(!strncmp(msg, "/pm ", 4))
{
- pm_open(&msg[4], 1, data->scroll);
+ pm_open(&msg[4], 1);
gtk_entry_set_text(entry, "");
sendingmsg=0;
return;
@@ -886,7 +894,7 @@ void captcha_done(GtkWidget* button, void* x)
int main(int argc, char** argv)
{
if(!strncmp(argv[0], "./", 2)){frombuild=1;}
- struct viddata datax={0,0,0,0,0};
+ struct viddata datax={0,0,0};
data=&datax;
avcodec_register_all();
data->vdecoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
@@ -977,18 +985,11 @@ int main(int argc, char** argv)
cambox=GTK_WIDGET(gtk_builder_get_object(gui, "cambox"));
userlistwidget=GTK_WIDGET(gtk_builder_get_object(gui, "userlistbox"));
GtkWidget* chatview=GTK_WIDGET(gtk_builder_get_object(gui, "chatview"));
- data->scroll=gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gtk_builder_get_object(gui, "chatscroll")));
- // Set up handling of links in chat
- g_signal_connect(chatview, "button-release-event", G_CALLBACK(gui_click_link), 0);
- g_signal_connect(chatview, "button-press-event", G_CALLBACK(gui_rightclick_link), 0);
- g_signal_connect(chatview, "motion-notify-event", G_CALLBACK(gui_hover_link), 0);
+ mainchat=chatview_new(GTK_TEXT_VIEW(chatview));
GdkDisplay* display=gtk_widget_get_display(chatview);
gui_cursor_text=gdk_cursor_new_from_name(display, "text");
gui_cursor_link=gdk_cursor_new_from_name(display, "pointer");
- data->buffer=gtk_text_view_get_buffer(GTK_TEXT_VIEW(chatview));
- buffer_setup_colors(data->buffer);
-
GtkWidget* panes=GTK_WIDGET(gtk_builder_get_object(gui, "vpaned"));
g_signal_connect(panes, "notify::position", G_CALLBACK(handleresizepane), data);
gtk_paned_set_wide_handle(GTK_PANED(panes), 1);
diff --git a/utilities/gtk/gui.c b/utilities/gtk/gui.c
index d51534a..0303173 100644
--- a/utilities/gtk/gui.c
+++ b/utilities/gtk/gui.c
@@ -34,23 +34,6 @@ extern gboolean gui_greenscreen_preview(void* x);
GdkCursor* gui_cursor_text;
GdkCursor* gui_cursor_link;
-char autoscroll_before(GtkAdjustment* scroll)
-{
- // Figure out if we're at the bottom and should autoscroll with new content
- int upper=gtk_adjustment_get_upper(scroll);
- int size=gtk_adjustment_get_page_size(scroll);
- int value=gtk_adjustment_get_value(scroll);
- return (value+size+20>=upper);
-}
-
-void autoscroll_after(GtkAdjustment* scroll)
-{
- while(gtk_events_pending()){gtk_main_iteration();} // Make sure the textview's new size affects scroll's "upper" value first
- int upper=gtk_adjustment_get_upper(scroll);
- int size=gtk_adjustment_get_page_size(scroll);
- gtk_adjustment_set_value(scroll, upper-size);
-}
-
void settings_reset(GtkBuilder* gui)
{
// Font
@@ -336,56 +319,41 @@ void pm_close(GtkButton* btn, GtkWidget* tab)
gtk_widget_destroy(tab);
struct user* user=user_find_by_tab(tab);
if(!user){return;}
+ free(user->pm_chatview);
user->pm_tab=0;
user->pm_tablabel=0;
- user->pm_buffer=0;
- user->pm_scroll=0;
+ user->pm_chatview=0;
user->pm_highlight=0;
}
-void pm_open(const char* nick, char select, GtkAdjustment* scroll)
+void pm_open(const char* nick, char select)
{
struct user* user=finduser(nick);
if(!user){return;}
GtkNotebook* tabs=GTK_NOTEBOOK(gtk_builder_get_object(gui, "tabs"));
- if(user->pm_tab)
+ if(!user->pm_tab)
{
- if(!select){return;}
- int num=gtk_notebook_page_num(tabs, user->pm_tab);
- gtk_notebook_set_current_page(tabs, num);
- return;
+ user->pm_chatview=chatview_new(0);
+ user->pm_tab=user->pm_chatview->scrolledwindow;
+ user->pm_tablabel=gtk_label_new(nick);
+ GtkWidget* tabbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ #if GTK_MAJOR_VERSION<3 || (GTK_MAJOR_VERSION==3 && GTK_MINOR_VERSION<10)
+ GtkWidget* closebtn=gtk_button_new_from_icon_name("gtk-close", GTK_ICON_SIZE_BUTTON);
+ #else
+ GtkWidget* closebtn=gtk_button_new_from_icon_name("window-close", GTK_ICON_SIZE_BUTTON);
+ #endif
+ g_signal_connect(closebtn, "clicked", G_CALLBACK(pm_close), user->pm_tab);
+ gtk_box_pack_start(GTK_BOX(tabbox), user->pm_tablabel, 1, 1, 0);
+ gtk_box_pack_start(GTK_BOX(tabbox), closebtn, 0, 0, 0);
+ gtk_notebook_append_page(tabs, user->pm_tab, tabbox);
+ gtk_widget_show_all(user->pm_tab);
+ gtk_widget_show_all(tabbox);
}
- char bottom=autoscroll_before(scroll); // If PM tabs (with close buttons) are taller we need to make sure pushing down the chat field doesn't make it stop scrolling
- GtkWidget* textview=gtk_text_view_new();
- g_signal_connect(textview, "button-release-event", G_CALLBACK(gui_click_link), 0);
- g_signal_connect(textview, "button-press-event", G_CALLBACK(gui_rightclick_link), 0);
- g_signal_connect(textview, "motion-notify-event", G_CALLBACK(gui_hover_link), 0);
- user->pm_tab=gtk_scrolled_window_new(0, 0);
- user->pm_buffer=gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
- user->pm_scroll=gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(user->pm_tab));
- user->pm_tablabel=gtk_label_new(nick);
- buffer_setup_colors(user->pm_buffer);
- gtk_container_add(GTK_CONTAINER(user->pm_tab), textview);
- GtkWidget* tabbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
-#if GTK_MAJOR_VERSION<3 || (GTK_MAJOR_VERSION==3 && GTK_MINOR_VERSION<10)
- GtkWidget* closebtn=gtk_button_new_from_icon_name("gtk-close", GTK_ICON_SIZE_BUTTON);
-#else
- GtkWidget* closebtn=gtk_button_new_from_icon_name("window-close", GTK_ICON_SIZE_BUTTON);
-#endif
- g_signal_connect(closebtn, "clicked", G_CALLBACK(pm_close), user->pm_tab);
- gtk_box_pack_start(GTK_BOX(tabbox), user->pm_tablabel, 1, 1, 0);
- gtk_box_pack_start(GTK_BOX(tabbox), closebtn, 0, 0, 0);
- int num=gtk_notebook_append_page(tabs, user->pm_tab, tabbox);
- gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), 0);
- gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview), 0);
- gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_CHAR);
- gtk_widget_show_all(user->pm_tab);
- gtk_widget_show_all(tabbox);
if(select)
{
+ int num=gtk_notebook_page_num(tabs, user->pm_tab);
gtk_notebook_set_current_page(tabs, num);
}
- if(bottom){autoscroll_after(scroll);}
}
void pm_highlight(const char* nick)
@@ -418,36 +386,6 @@ char pm_select(GtkNotebook* tabs, GtkWidget* tab, int num, void* x)
return 0;
}
-void buffer_setup_colors(GtkTextBuffer* buffer)
-{
- #define colormap(code, color) gtk_text_buffer_create_tag(buffer, code, "foreground", color, (char*)0)
- colormap("[31", "#821615");
- colormap("[31;1", "#c53332");
- colormap("[33", "#a08f23");
- //colormap("[33", "#a78901");
- colormap("[33;1", "#919104");
- colormap("[32;1", "#7bb224");
- //colormap("[32;1", "#7db257");
- colormap("[32", "#487d21");
- colormap("[36", "#00a990");
- colormap("[34;1", "#32a5d9");
- //colormap("[34;1", "#1d82eb");
- colormap("[34", "#1965b6");
- colormap("[35", "#5c1a7a");
- colormap("[35;1", "#9d5bb5");
- //colormap("[35;1", "#c356a3");
- //colormap("[35;1", "#b9807f");
- colormap("timestamp", "#808080");
- gtk_text_buffer_create_tag(buffer, "nickname", "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, (char*)0);
- // Set size if it's set in config
- if(config_get_set("fontsize"))
- {
- gtk_text_buffer_create_tag(buffer, "size", "size-points", config_get_double("fontsize"), "size-set", TRUE, (char*)0);
- }else{
- gtk_text_buffer_create_tag(buffer, "size", "size-set", FALSE, (char*)0);
- }
-}
-
void buffer_updatesize(GtkTextBuffer* buffer)
{
GtkTextIter start, end;
@@ -468,11 +406,12 @@ void fontsize_set(double size)
unsigned int i;
for(i=0; i<usercount; ++i)
{
- if(!userlist[i].pm_buffer){continue;}
- table=gtk_text_buffer_get_tag_table(userlist[i].pm_buffer);
+ if(!userlist[i].pm_chatview){continue;}
+ buffer=gtk_text_view_get_buffer(userlist[i].pm_chatview->textview);
+ table=gtk_text_buffer_get_tag_table(buffer);
tag=gtk_text_tag_table_lookup(table, "size");
g_object_set(tag, "size-points", size, "size-set", TRUE, (char*)0);
- buffer_updatesize(userlist[i].pm_buffer);
+ buffer_updatesize(buffer);
}
}
@@ -767,3 +706,68 @@ void gui_link_menu_copy(GtkWidget* menuitem, void* x)
GtkClipboard* clipboard=gtk_clipboard_get_for_display(gtk_widget_get_display(menuitem), GDK_SELECTION_CLIPBOARD);
gtk_clipboard_set_text(clipboard, gui_link_menu_url, -1);
}
+
+void chatview_scrolled(GtkAdjustment* adj, struct chatview* cv)
+{
+ double value=gtk_adjustment_get_value(adj);
+ double upper=gtk_adjustment_get_upper(adj);
+ double pagesize=gtk_adjustment_get_page_size(adj);
+ char bottom=(value+pagesize>=upper);
+ // To keep smooth scrolling from messing up autoscroll,
+ // don't change state if we're already marked as being at the
+ // bottom and scrolling down.
+ if(bottom || value<cv->oldscrollposition)
+ {
+ cv->atbottom=bottom;
+ cv->oldscrollposition=value;
+ }
+}
+
+struct chatview* chatview_new(GtkTextView* existing_textview)
+{
+ struct chatview* this=malloc(sizeof(struct chatview));
+ if(existing_textview)
+ {
+ this->textview=existing_textview;
+ this->scrolledwindow=gtk_widget_get_parent(GTK_WIDGET(this->textview));
+ }else{
+ this->textview=GTK_TEXT_VIEW(gtk_text_view_new());
+ this->scrolledwindow=gtk_scrolled_window_new(0, 0);
+ gtk_container_add(GTK_CONTAINER(this->scrolledwindow), GTK_WIDGET(this->textview));
+ }
+ this->atbottom=1;
+ gtk_text_view_set_editable(this->textview, 0);
+ gtk_text_view_set_cursor_visible(this->textview, 0);
+ gtk_text_view_set_wrap_mode(this->textview, GTK_WRAP_CHAR);
+ GtkAdjustment* scroll=gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(this->scrolledwindow));
+ g_signal_connect(this->textview, "button-release-event", G_CALLBACK(gui_click_link), 0);
+ g_signal_connect(this->textview, "button-press-event", G_CALLBACK(gui_rightclick_link), 0);
+ g_signal_connect(this->textview, "motion-notify-event", G_CALLBACK(gui_hover_link), 0);
+ g_signal_connect(scroll, "value-changed", G_CALLBACK(chatview_scrolled), this);
+
+ // Set up the buffer
+ GtkTextBuffer* buffer=gtk_text_view_get_buffer(this->textview);
+ gtk_text_buffer_create_tag(buffer, "timestamp", "foreground", "#808080", (char*)0);
+ gtk_text_buffer_create_tag(buffer, "nickname", "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, (char*)0);
+ // Set size if it's set in config
+ if(config_get_set("fontsize"))
+ {
+ gtk_text_buffer_create_tag(buffer, "size", "size-points", config_get_double("fontsize"), "size-set", TRUE, (char*)0);
+ }else{
+ gtk_text_buffer_create_tag(buffer, "size", "size-set", FALSE, (char*)0);
+ }
+ // And en 'end' mark for scrolling
+ GtkTextIter end;
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ gtk_text_buffer_create_mark(buffer, "end", &end, 0);
+
+ return this;
+}
+
+void chatview_autoscroll(struct chatview* cv)
+{
+ if(!cv->atbottom){return;}
+ GtkTextBuffer* buffer=gtk_text_view_get_buffer(cv->textview);
+ GtkTextMark* mark=gtk_text_buffer_get_mark(buffer, "end");
+ gtk_text_view_scroll_to_mark(cv->textview, mark, 0, 0, 0, 0);
+}
diff --git a/utilities/gtk/gui.h b/utilities/gtk/gui.h
index 33af3ef..e0071fb 100644
--- a/utilities/gtk/gui.h
+++ b/utilities/gtk/gui.h
@@ -22,8 +22,15 @@ struct channelopts
char save;
};
-extern char autoscroll_before(GtkAdjustment* scroll);
-extern void autoscroll_after(GtkAdjustment* scroll);
+struct chatview
+{
+ GtkTextView* textview;
+ GtkWidget* scrolledwindow;
+ char atbottom; // If we're scrolled to the bottom we should stay at the bottom
+ // unless the user scrolls up
+ double oldscrollposition; // To keep smooth scrolling from messing things up
+};
+
extern void settings_reset(GtkBuilder* gui);
extern void showsettings(GtkMenuItem* item, GtkBuilder* gui);
extern void savesettings(GtkButton* button, GtkBuilder* gui);
@@ -32,10 +39,9 @@ extern void toggle_logging(GtkToggleButton* button, GtkBuilder* gui);
extern void toggle_youtubecmd(GtkToggleButton* button, GtkBuilder* gui);
extern void deletechannel(GtkButton* button, void* x);
extern void channeldialog(GtkButton* button, struct channelopts* opts);
-extern void pm_open(const char* nick, char select, GtkAdjustment* scroll);
+extern void pm_open(const char* nick, char select);
extern void pm_highlight(const char* nick);
extern char pm_select(GtkNotebook* tabs, GtkWidget* tab, int num, void* x);
-extern void buffer_setup_colors(GtkTextBuffer* buffer);
extern void buffer_updatesize(GtkTextBuffer* buffer);
extern void fontsize_set(double size);
extern gboolean gui_show_cam_menu(GtkWidget* widget, GdkEventButton* event, const char* id);
@@ -54,6 +60,8 @@ extern gboolean gui_rightclick_link(GtkTextView* textview, GdkEventButton* event
extern gboolean gui_hover_link(GtkTextView* textview, GdkEventMotion* event, void* data);
extern void gui_link_menu_open(GtkWidget* menuitem, void* x);
extern void gui_link_menu_copy(GtkWidget* menuitem, void* x);
+extern struct chatview* chatview_new(GtkTextView* existing_textview);
+extern void chatview_autoscroll(struct chatview* cv);
extern GtkBuilder* gui;
extern GdkCursor* gui_cursor_text;
diff --git a/utilities/gtk/userlist.c b/utilities/gtk/userlist.c
index 0aac809..1121749 100644
--- a/utilities/gtk/userlist.c
+++ b/utilities/gtk/userlist.c
@@ -1,6 +1,6 @@
/*
camviewer, a sample application to view tinychat cam streams
- Copyright (C) 2015 alicia@ion.nu
+ Copyright (C) 2015-2016 alicia@ion.nu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -54,8 +54,7 @@ struct user* adduser(const char* nick)
userlist[usercount-1].label=gtk_label_new(nick); // TODO: some kind of menubutton for actions?
userlist[usercount-1].pm_tab=0;
userlist[usercount-1].pm_tablabel=0;
- userlist[usercount-1].pm_buffer=0;
- userlist[usercount-1].pm_scroll=0;
+ userlist[usercount-1].pm_chatview=0;
userlist[usercount-1].pm_highlight=0;
#if GTK_MAJOR_VERSION>=3
gtk_widget_set_halign(userlist[usercount-1].label, GTK_ALIGN_START);
diff --git a/utilities/gtk/userlist.h b/utilities/gtk/userlist.h
index eb229c6..0b58dbe 100644
--- a/utilities/gtk/userlist.h
+++ b/utilities/gtk/userlist.h
@@ -1,6 +1,6 @@
/*
camviewer, a sample application to view tinychat cam streams
- Copyright (C) 2015 alicia@ion.nu
+ Copyright (C) 2015-2016 alicia@ion.nu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
@@ -18,12 +18,10 @@ struct user
{
char* nick;
GtkWidget* label;
-// unsigned int id; // hm, tc_client doesn't share IDs other than in guestnicks, this might be useful for a ban-after-they-left situation
char ismod;
GtkWidget* pm_tab;
GtkWidget* pm_tablabel;
- GtkTextBuffer* pm_buffer;
- GtkAdjustment* pm_scroll;
+ struct chatview* pm_chatview;
char pm_highlight;
};