556 lines
13 KiB
C
556 lines
13 KiB
C
/* gtkSlash - Slashdot headlines news-ticker
|
|
* (c) 1998 Troy Engel <tengel@sonic.net>
|
|
*
|
|
* Based on original code from slashes.pl
|
|
* (c) 1998 Alex Shnitman <alexsh@linux.org.il>
|
|
*
|
|
* Other cool code from grun
|
|
* (c) 1998 Southern Gold Development <tangomanrulz@geocities.com>
|
|
*
|
|
* This code is distributed under the terms of the GNU
|
|
* General Public License, etc etc. Use it, don't abuse it.
|
|
*/
|
|
|
|
/* just what it says - good for layout development
|
|
* just remember to turn it off :P */
|
|
/* #define _LOCAL_TEST_MODE */
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
#define DIR_CHAR "\\"
|
|
#define DOT_CHAR "_"
|
|
#else
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#define DIR_CHAR "/"
|
|
#define DOT_CHAR "."
|
|
/* these two allow Win people to "hide" a cmdline
|
|
* app. Provided here just so it works (could be used
|
|
* for the same thing in 'startApp' on unix?)
|
|
*/
|
|
#define SW_SHOWDEFAULT 0
|
|
#define SW_HIDE 0
|
|
#endif
|
|
|
|
#include "rcfuncs.h"
|
|
|
|
#define MAX_BUFF 1024
|
|
#define APP_RC "gtkslashrc"
|
|
|
|
#define MSG_TITLE "Slashdot Headlines"
|
|
#define MSG_INIT "Initializing..."
|
|
#define MSG_LOAD "Loading ultramode.txt..."
|
|
#define MSG_LOAD_ERR "Error loading ultramode.txt!"
|
|
#define MSG_RETRIEVE "Retrieving latest ultramode.txt..."
|
|
#define MSG_RETR_ERR "Error retrieving ultramode.txt!"
|
|
#define MSG_LAUNCH "Launching web browser..."
|
|
#define MSG_LAUNCH_ERR "Error launching browser!"
|
|
#define MSG_IDLE "Status: Idle (gtkSlash 0.5.5)"
|
|
|
|
/* read from RC file */
|
|
gint refresh_timeout;
|
|
gchar browser_cmd[MAX_BUFF];
|
|
gchar new_browser_cmd[MAX_BUFF];
|
|
gchar curl_cmd[MAX_BUFF];
|
|
BOOL compact_mode;
|
|
gint num_fields;
|
|
BOOL comments_flat;
|
|
|
|
/* some ugly global vars */
|
|
GtkWidget *status;
|
|
|
|
/* retrieves a line from a linefeed delimited textfile */
|
|
static
|
|
gchar *app_getLine(FILE *file) {
|
|
char *tmp, in;
|
|
int cnt = 0, retIn;
|
|
|
|
if (feof(file)) {
|
|
return NULL;
|
|
}
|
|
else {
|
|
tmp = g_malloc(sizeof(char) * MAX_BUFF);
|
|
retIn = fread((void *) &in, sizeof(char), 1, file);
|
|
if (retIn != 1) {
|
|
g_free(tmp);
|
|
return NULL;
|
|
}
|
|
while ((in != '\n') && (cnt < MAX_BUFF)) {
|
|
tmp[cnt] = in;
|
|
cnt++;
|
|
retIn = fread((void *) &in, sizeof(char), 1, file);
|
|
if (retIn != 1) {
|
|
g_free(tmp);
|
|
return NULL;
|
|
}
|
|
}
|
|
if (cnt == MAX_BUFF) {
|
|
g_free(tmp);
|
|
return NULL;
|
|
}
|
|
tmp[cnt] = '\0';
|
|
return tmp;
|
|
}
|
|
}
|
|
|
|
/* loads ultramode.txt into our clist */
|
|
static
|
|
BOOL app_loadFile(GtkWidget *list, const gchar *fname) {
|
|
gchar *line;
|
|
gchar *article[4], *link;
|
|
FILE *fHnd;
|
|
gint i;
|
|
|
|
fHnd = fopen(fname, "rb");
|
|
if (!fHnd) {
|
|
return FALSE;
|
|
}
|
|
line = app_getLine(fHnd);
|
|
if (!line) {
|
|
fclose(fHnd);
|
|
return FALSE;
|
|
}
|
|
/* loop till we find the first record delim */
|
|
while (strncmp(line, "%%", 2) != 0) {
|
|
g_free(line);
|
|
line = app_getLine(fHnd);
|
|
}
|
|
gtk_clist_clear(GTK_CLIST(list));
|
|
gtk_clist_freeze(GTK_CLIST(list));
|
|
link = NULL;
|
|
while (line) {
|
|
gboolean bGotit = FALSE;
|
|
g_free(line); /* should be %% */
|
|
for (i = 0 ; i<num_fields; i++) {
|
|
line = app_getLine(fHnd);
|
|
if (line) {
|
|
bGotit = TRUE;
|
|
switch (i) {
|
|
case 0: /* title */
|
|
article[0] = line;
|
|
break;
|
|
case 1: /* link */
|
|
link = line;
|
|
break;
|
|
case 3: /* author */
|
|
article[1] = line;
|
|
break;
|
|
case 5: /* topic */
|
|
article[2] = line;
|
|
break;
|
|
case 6: /* numcomments */
|
|
article[3] = line;
|
|
break;
|
|
default: /* unused */
|
|
g_free(line);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (bGotit) {
|
|
gint index;
|
|
index = gtk_clist_append(GTK_CLIST(list), article);
|
|
gtk_clist_set_row_data(GTK_CLIST(list), index, link);
|
|
for (i=0; i<4; i++) {
|
|
if (article[i])
|
|
g_free(article[i]);
|
|
}
|
|
}
|
|
line = app_getLine(fHnd);
|
|
}
|
|
fclose(fHnd);
|
|
gtk_clist_thaw(GTK_CLIST(list));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* launches a given app */
|
|
static
|
|
BOOL app_startApp(const gchar *cmd, BOOL bWait, WORD wShowState) {
|
|
#ifndef WIN32
|
|
char **args, *work, *twrk;
|
|
int cnt, len, scnt, pid, sta;
|
|
|
|
pid = fork();
|
|
if (pid == 0) {
|
|
/* if not waiting for return of app, fork() again to
|
|
* detach completely */
|
|
if (!bWait) {
|
|
pid = fork();
|
|
}
|
|
if (pid == 0) {
|
|
len = strlen(cmd);
|
|
scnt = 1;
|
|
for (cnt = 0; cnt < len; cnt++) {
|
|
if (cmd[cnt] == ' ') {
|
|
scnt++;
|
|
}
|
|
}
|
|
args = g_malloc(sizeof(char *) * (scnt + 1));
|
|
args[scnt] = NULL;
|
|
if (scnt == 1) {
|
|
args[0] = g_malloc(sizeof(char) * (len + 1));
|
|
strcpy(args[0], cmd);
|
|
} else {
|
|
work = g_malloc(sizeof(char) * (len + 1));
|
|
strcpy(work, cmd);
|
|
twrk = (char *) strtok(work, " ");
|
|
args[0] = twrk;
|
|
cnt = 1;
|
|
while (twrk) {
|
|
twrk = (char *) strtok(NULL, " ");
|
|
args[cnt] = twrk;
|
|
cnt++;
|
|
}
|
|
args[cnt] = work;
|
|
}
|
|
execvp(args[0], args);
|
|
} else {
|
|
_exit(0);
|
|
}
|
|
} else {
|
|
if (bWait) {
|
|
pid = wait(&sta);
|
|
return WIFEXITED(sta);
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
#else /* Win32 */
|
|
STARTUPINFO startupinfo;
|
|
PROCESS_INFORMATION processinfo;
|
|
|
|
startupinfo.cb = sizeof (STARTUPINFO);
|
|
startupinfo.lpReserved = NULL;
|
|
startupinfo.lpDesktop = NULL;
|
|
startupinfo.lpTitle = NULL;
|
|
startupinfo.dwFlags = STARTF_USESHOWWINDOW;
|
|
startupinfo.wShowWindow = wShowState;
|
|
startupinfo.cbReserved2 = 0;
|
|
startupinfo.lpReserved2 = NULL;
|
|
|
|
if (!CreateProcess (NULL, (gchar *)cmd, NULL, NULL,
|
|
TRUE, NORMAL_PRIORITY_CLASS, NULL,
|
|
NULL, &startupinfo, &processinfo)) {
|
|
g_message ("CreateProcess failed\n");
|
|
return FALSE;
|
|
}
|
|
CloseHandle (processinfo.hThread);
|
|
if (bWait)
|
|
WaitForSingleObject(processinfo.hProcess, INFINITE);
|
|
return TRUE;
|
|
#endif /* WIN32 */
|
|
}
|
|
|
|
/* uses curl to get ultramode from the web */
|
|
static
|
|
BOOL app_getFile(const gchar *fname) {
|
|
gchar cmd[MAX_BUFF];
|
|
sprintf(cmd, curl_cmd, fname);
|
|
if (!app_startApp(cmd, TRUE, SW_HIDE))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static
|
|
BOOL app_isZeroByte(const gchar *fname) {
|
|
FILE *fHnd;
|
|
gint ch = 0;
|
|
fHnd = fopen(fname, "rb");
|
|
if (!fHnd) {
|
|
return TRUE;
|
|
}
|
|
ch = fgetc(fHnd);
|
|
if (ch == EOF) {
|
|
fclose(fHnd);
|
|
return TRUE;
|
|
}
|
|
fclose(fHnd);
|
|
return FALSE;
|
|
}
|
|
|
|
static
|
|
gint app_timer_idle(gpointer data) {
|
|
gtk_label_set(GTK_LABEL(status), MSG_IDLE);
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
gint app_exit(GtkWidget *widget, GdkEvent *event, gpointer data) {
|
|
gtk_main_quit();
|
|
exit(0);
|
|
return FALSE;
|
|
}
|
|
|
|
static
|
|
gint app_timer_refresh(GtkWidget *list) {
|
|
gchar *home_env, *fname;
|
|
gint retval;
|
|
#ifdef WIN32
|
|
gchar mypath[MAX_BUFF];
|
|
#endif
|
|
|
|
home_env = g_get_home_dir();
|
|
#ifdef WIN32 /* no HOME dir */
|
|
if (home_env == NULL) {
|
|
gchar *cutme;
|
|
GetModuleFileName(NULL, mypath, sizeof(mypath));
|
|
cutme = strrchr(mypath, '\\');
|
|
mypath[cutme - mypath] = '\0';
|
|
home_env = mypath;
|
|
}
|
|
#endif
|
|
fname = g_malloc(sizeof(gchar) * (strlen(home_env) + 15));
|
|
strcpy(fname, home_env);
|
|
strcat(fname, DIR_CHAR);
|
|
strcat(fname, "ultramode.tmp");
|
|
|
|
retval = TRUE;
|
|
#ifndef _LOCAL_TEST_MODE
|
|
remove(fname);
|
|
gtk_label_set(GTK_LABEL(status), MSG_RETRIEVE);
|
|
if (!app_getFile(fname) || app_isZeroByte(fname)) {
|
|
gtk_label_set(GTK_LABEL(status), MSG_RETR_ERR);
|
|
retval = FALSE;
|
|
}
|
|
#endif
|
|
gtk_label_set(GTK_LABEL(status), MSG_LOAD);
|
|
if (retval && !app_loadFile(list, fname)) {
|
|
gtk_label_set(GTK_LABEL(status), MSG_LOAD_ERR);
|
|
retval = FALSE;
|
|
}
|
|
#ifndef _LOCAL_TEST_MODE
|
|
remove(fname);
|
|
#endif
|
|
g_free(fname);
|
|
if (retval)
|
|
gtk_label_set(GTK_LABEL(status), MSG_IDLE);
|
|
else
|
|
gtk_timeout_add(1000*5, app_timer_idle, NULL);
|
|
return retval;
|
|
}
|
|
|
|
static
|
|
gint app_refresh(GtkWidget *widget, GtkWidget *list) {
|
|
return app_timer_refresh(list);
|
|
}
|
|
|
|
/* launch our article */
|
|
static
|
|
void app_article_browse(GtkWidget *widget, gpointer data) {
|
|
gint rerr;
|
|
struct stat buff;
|
|
gchar *url, *fname, *home;
|
|
gchar cmd[MAX_BUFF];
|
|
|
|
url = gtk_clist_get_row_data(GTK_CLIST(data), GTK_CLIST(data)->focus_row);
|
|
if (comments_flat) {
|
|
gchar *tmp;
|
|
tmp = strrchr(url, '.');
|
|
url[tmp - url] = '\0';
|
|
strcat(url, "_F.shtml");
|
|
}
|
|
#ifndef WIN32
|
|
home = g_get_home_dir();
|
|
fname = g_malloc(sizeof(gchar) * (strlen(home) + 16));
|
|
strcpy(fname, home);
|
|
strcat(fname, "/.netscape/lock");
|
|
rerr = lstat(fname, &buff);
|
|
if (rerr == -1) {
|
|
sprintf(cmd, new_browser_cmd, url);
|
|
} else {
|
|
sprintf(cmd, browser_cmd, url);
|
|
}
|
|
g_free(fname);
|
|
#else
|
|
sprintf(cmd, browser_cmd, url);
|
|
#endif /* WIN32 */
|
|
gtk_label_set(GTK_LABEL(status), MSG_LAUNCH);
|
|
if (!app_startApp(cmd, FALSE, SW_SHOWDEFAULT))
|
|
gtk_label_set(GTK_LABEL(status), MSG_LAUNCH_ERR);
|
|
gtk_timeout_add(1000*5, app_timer_idle, NULL);
|
|
}
|
|
|
|
/* sorting */
|
|
static
|
|
void app_click_column(GtkCList *clist, gint column, gpointer data) {
|
|
if (column == clist->sort_column) {
|
|
clist->sort_type = (clist->sort_type == GTK_SORT_ASCENDING) ?
|
|
GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
|
|
} else {
|
|
gtk_clist_set_sort_column (clist, column);
|
|
}
|
|
gtk_clist_sort(clist);
|
|
}
|
|
|
|
static
|
|
BOOL app_parse_rc() {
|
|
gchar *home_env, *fname;
|
|
gint retval;
|
|
|
|
home_env = g_get_home_dir();
|
|
/* /home/foobar + /. + myrc + \0 */
|
|
fname = g_malloc(sizeof(gchar) * (strlen(home_env)+2+strlen(APP_RC)+1));
|
|
strcpy(fname, home_env);
|
|
strcat(fname, DIR_CHAR);
|
|
strcat(fname, DOT_CHAR);
|
|
strcat(fname, APP_RC);
|
|
|
|
retval = RCInit(fname);
|
|
g_free(fname);
|
|
|
|
/* if no personal file, look for system */
|
|
if (!retval) {
|
|
#ifdef WIN32
|
|
gchar tmp[MAX_BUFF];
|
|
GetWindowsDirectory(tmp, sizeof(tmp));
|
|
/* c:\windows + \ + myrc + \0 */
|
|
fname = g_malloc(sizeof(gchar) * (sizeof(tmp) + 1 + sizeof(APP_RC) + 1));
|
|
strcpy(fname, tmp);
|
|
#else
|
|
/* /etc + / + myrc + \0 */
|
|
fname = g_malloc(sizeof(gchar) * (4 + 1 + strlen(APP_RC) + 1));
|
|
strcpy(fname, "/etc");
|
|
#endif
|
|
strcat(fname, DIR_CHAR);
|
|
strcat(fname, APP_RC);
|
|
retval = RCInit(fname);
|
|
g_free(fname);
|
|
}
|
|
|
|
refresh_timeout = RCGetInt("timeout-refresh", 1800);
|
|
compact_mode = RCGetBool("compact-mode", FALSE);
|
|
num_fields = RCGetInt("num-fields", 9);
|
|
RCGetString("browser-cmd", browser_cmd, "", sizeof(browser_cmd));
|
|
RCGetString("new-browser-cmd", new_browser_cmd, "", sizeof(new_browser_cmd));
|
|
RCGetString("curl-cmd", curl_cmd, "", sizeof(curl_cmd));
|
|
comments_flat = RCGetBool("comments-flat", FALSE);
|
|
|
|
RCShutDown();
|
|
return retval;
|
|
}
|
|
|
|
static
|
|
void app_window() {
|
|
GtkWidget *window, *vbox, *clist, *scrolled_win, *hbox, *but;
|
|
gchar *titles[4] = {"Title", "Author", "Topic", "Comments"};
|
|
|
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title(GTK_WINDOW(window), MSG_TITLE);
|
|
gtk_signal_connect(GTK_OBJECT(window), "destroy", (GtkSignalFunc) app_exit, NULL);
|
|
gtk_widget_set_usize(GTK_WIDGET(window), 550, 155);
|
|
gtk_window_set_policy(GTK_WINDOW (window), TRUE, TRUE, TRUE);
|
|
|
|
vbox = gtk_vbox_new(FALSE, 5);
|
|
gtk_container_border_width (GTK_CONTAINER(vbox), 5);
|
|
gtk_container_add(GTK_CONTAINER(window), vbox);
|
|
|
|
scrolled_win = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win),
|
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
|
gtk_box_pack_start(GTK_BOX(vbox), scrolled_win, TRUE, TRUE, 0);
|
|
|
|
clist = gtk_clist_new_with_titles(4, titles);
|
|
gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_BROWSE);
|
|
gtk_clist_set_column_width(GTK_CLIST(clist), 0, 250);
|
|
gtk_clist_set_column_width(GTK_CLIST(clist), 1, 70);
|
|
gtk_clist_set_column_width(GTK_CLIST(clist), 2, 100);
|
|
gtk_clist_set_column_width(GTK_CLIST(clist), 3, 10);
|
|
gtk_signal_connect(GTK_OBJECT (clist), "click_column",
|
|
(GtkSignalFunc)app_click_column, NULL);
|
|
gtk_container_add(GTK_CONTAINER(scrolled_win), clist);
|
|
gtk_widget_show(clist);
|
|
gtk_widget_show(scrolled_win);
|
|
|
|
if (!compact_mode) {
|
|
hbox = gtk_hbox_new(FALSE, 0);
|
|
but = gtk_button_new_with_label("Refresh");
|
|
gtk_widget_set_usize(GTK_WIDGET(but), 70, 24);
|
|
gtk_signal_connect(GTK_OBJECT(but), "clicked", (GtkSignalFunc) app_refresh, clist);
|
|
gtk_box_pack_start(GTK_BOX(hbox), but, FALSE, FALSE, 0);
|
|
gtk_widget_show(but);
|
|
|
|
but = gtk_button_new_with_label("Read");
|
|
gtk_widget_set_usize(GTK_WIDGET(but), 70, 24);
|
|
gtk_signal_connect(GTK_OBJECT(but), "clicked", (GtkSignalFunc) app_article_browse, (gpointer)clist);
|
|
gtk_box_pack_start(GTK_BOX(hbox), but, FALSE, FALSE, 0);
|
|
gtk_widget_show(but);
|
|
|
|
status = gtk_label_new(MSG_INIT);
|
|
gtk_box_pack_start(GTK_BOX(hbox), status, FALSE, FALSE, 10);
|
|
gtk_widget_show(status);
|
|
|
|
but = gtk_button_new_with_label("Quit");
|
|
gtk_widget_set_usize(GTK_WIDGET(but), 70, 24);
|
|
gtk_signal_connect(GTK_OBJECT(but), "clicked", (GtkSignalFunc) app_exit, NULL);
|
|
gtk_box_pack_end(GTK_BOX(hbox), but, FALSE, FALSE, 0);
|
|
gtk_widget_show(but);
|
|
|
|
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
|
|
gtk_widget_show(hbox);
|
|
} else {
|
|
gtk_clist_column_titles_hide(GTK_CLIST(clist));
|
|
gtk_signal_connect(GTK_OBJECT (clist), "select_row", (GtkSignalFunc) app_article_browse, (gpointer)clist);
|
|
status = gtk_label_new("");
|
|
}
|
|
|
|
gtk_timeout_add(1000*refresh_timeout, (GtkFunction) app_timer_refresh, clist);
|
|
|
|
gtk_widget_show(vbox);
|
|
gtk_widget_show(window);
|
|
|
|
app_refresh(NULL, clist);
|
|
}
|
|
|
|
#ifdef WIN32
|
|
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nShowCmd) {
|
|
#else
|
|
int main(int argc, char **argv) {
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
/* parse into a command line ala argv, argc */
|
|
char **argv, *work, *twrk;
|
|
int cnt, len, argc;
|
|
|
|
len = strlen(lpCmdLine);
|
|
argc = 0;
|
|
for (cnt = 0; cnt < len; cnt++) {
|
|
if (lpCmdLine[cnt] == ' ') {
|
|
argc++;
|
|
}
|
|
}
|
|
argv = g_malloc(sizeof(char *) * (argc + 1));
|
|
argv[argc] = NULL;
|
|
if (argc == 1) {
|
|
argv[0] = g_malloc(sizeof(char) * (len + 1));
|
|
strcpy(argv[0], lpCmdLine);
|
|
} else {
|
|
work = g_malloc(sizeof(char) * (len + 1));
|
|
strcpy(work, lpCmdLine);
|
|
twrk = (char *) strtok(work, " ");
|
|
argv[0] = twrk;
|
|
cnt = 1;
|
|
while (twrk) {
|
|
twrk = (char *) strtok(NULL, " ");
|
|
argv[cnt] = twrk;
|
|
cnt++;
|
|
}
|
|
argv[cnt] = work;
|
|
}
|
|
#endif /* WIN32 */
|
|
|
|
gtk_init(&argc, &argv);
|
|
app_parse_rc();
|
|
app_window();
|
|
gtk_main();
|
|
return(0) ;
|
|
}
|