mirror of
https://github.com/moparisthebest/wget
synced 2024-07-03 16:38:41 -04:00
585 lines
14 KiB
C
585 lines
14 KiB
C
|
/* Download progress.
|
|||
|
Copyright (C) 2001 Free Software Foundation, Inc.
|
|||
|
|
|||
|
This file is part of GNU Wget.
|
|||
|
|
|||
|
GNU Wget is free software; you can redistribute it and/or modify
|
|||
|
it under the terms of the GNU General Public License as published by
|
|||
|
the Free Software Foundation; either version 2 of the License, or
|
|||
|
\(at your option) any later version.
|
|||
|
|
|||
|
GNU Wget is distributed in the hope that it will be useful,
|
|||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
GNU General Public License for more details.
|
|||
|
|
|||
|
You should have received a copy of the GNU General Public License
|
|||
|
along with Wget; if not, write to the Free Software
|
|||
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
|
|||
|
|
|||
|
#include <config.h>
|
|||
|
|
|||
|
#include <stdio.h>
|
|||
|
#include <stdlib.h>
|
|||
|
#ifdef HAVE_STRING_H
|
|||
|
# include <string.h>
|
|||
|
#else
|
|||
|
# include <strings.h>
|
|||
|
#endif /* HAVE_STRING_H */
|
|||
|
#include <assert.h>
|
|||
|
|
|||
|
#include "wget.h"
|
|||
|
#include "progress.h"
|
|||
|
#include "utils.h"
|
|||
|
#include "retr.h"
|
|||
|
|
|||
|
struct progress_implementation {
|
|||
|
char *name;
|
|||
|
void *(*create) (long, long);
|
|||
|
void (*update) (void *, long);
|
|||
|
void (*finish) (void *);
|
|||
|
void (*set_params) (const char *);
|
|||
|
};
|
|||
|
|
|||
|
/* Necessary forward declarations. */
|
|||
|
|
|||
|
static void *dp_create PARAMS ((long, long));
|
|||
|
static void dp_update PARAMS ((void *, long));
|
|||
|
static void dp_finish PARAMS ((void *));
|
|||
|
static void dp_set_params PARAMS ((const char *));
|
|||
|
|
|||
|
static void *bar_create PARAMS ((long, long));
|
|||
|
static void bar_update PARAMS ((void *, long));
|
|||
|
static void bar_finish PARAMS ((void *));
|
|||
|
static void bar_set_params PARAMS ((const char *));
|
|||
|
|
|||
|
static struct progress_implementation implementations[] = {
|
|||
|
{ "dot", dp_create, dp_update, dp_finish, dp_set_params },
|
|||
|
{ "bar", bar_create, bar_update, bar_finish, bar_set_params }
|
|||
|
};
|
|||
|
static struct progress_implementation *current_impl;
|
|||
|
|
|||
|
/* Return non-zero if NAME names a valid progress bar implementation.
|
|||
|
The characters after the first : will be ignored. */
|
|||
|
|
|||
|
int
|
|||
|
valid_progress_implementation_p (const char *name)
|
|||
|
{
|
|||
|
int i = 0;
|
|||
|
struct progress_implementation *pi = implementations;
|
|||
|
char *colon = strchr (name, ':');
|
|||
|
int namelen = colon ? colon - name : strlen (name);
|
|||
|
|
|||
|
for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
|
|||
|
if (!strncmp (pi->name, name, namelen))
|
|||
|
return 1;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Set the progress implementation to NAME. */
|
|||
|
|
|||
|
void
|
|||
|
set_progress_implementation (const char *name)
|
|||
|
{
|
|||
|
int i = 0;
|
|||
|
struct progress_implementation *pi = implementations;
|
|||
|
char *colon = strchr (name, ':');
|
|||
|
int namelen = colon ? colon - name : strlen (name);
|
|||
|
|
|||
|
for (i = 0; i < ARRAY_SIZE (implementations); i++, pi++)
|
|||
|
if (!strncmp (pi->name, name, namelen))
|
|||
|
{
|
|||
|
current_impl = pi;
|
|||
|
|
|||
|
if (colon)
|
|||
|
/* We call pi->set_params even if colon is NULL because we
|
|||
|
want to give the implementation a chance to set up some
|
|||
|
things it needs to run. */
|
|||
|
++colon;
|
|||
|
|
|||
|
if (pi->set_params)
|
|||
|
pi->set_params (colon);
|
|||
|
return;
|
|||
|
}
|
|||
|
abort ();
|
|||
|
}
|
|||
|
|
|||
|
/* Create a progress gauge. INITIAL is the number of bytes the
|
|||
|
download starts from (zero if the download starts from scratch).
|
|||
|
TOTAL is the expected total number of bytes in this download. If
|
|||
|
TOTAL is zero, it means that the download size is not known in
|
|||
|
advance. */
|
|||
|
|
|||
|
void *
|
|||
|
progress_create (long initial, long total)
|
|||
|
{
|
|||
|
return current_impl->create (initial, total);
|
|||
|
}
|
|||
|
|
|||
|
/* Inform the progress gauge of newly received bytes. */
|
|||
|
|
|||
|
void
|
|||
|
progress_update (void *progress, long howmuch)
|
|||
|
{
|
|||
|
current_impl->update (progress, howmuch);
|
|||
|
}
|
|||
|
|
|||
|
/* Tell the progress gauge to clean up. Calling this will free the
|
|||
|
PROGRESS object, the further use of which is not allowed. */
|
|||
|
|
|||
|
void
|
|||
|
progress_finish (void *progress)
|
|||
|
{
|
|||
|
current_impl->finish (progress);
|
|||
|
}
|
|||
|
|
|||
|
/* Dot-printing. */
|
|||
|
|
|||
|
struct dot_progress {
|
|||
|
long initial_length; /* how many bytes have been downloaded
|
|||
|
previously. */
|
|||
|
long total_length; /* expected total byte count when the
|
|||
|
download finishes */
|
|||
|
|
|||
|
int accumulated;
|
|||
|
|
|||
|
int rows; /* number of rows printed so far */
|
|||
|
int dots; /* number of dots printed in this row */
|
|||
|
|
|||
|
struct wget_timer *timer; /* timer used to measure per-row
|
|||
|
download rates. */
|
|||
|
long last_timer_value;
|
|||
|
};
|
|||
|
|
|||
|
/* Dot-progress backend for progress_create. */
|
|||
|
|
|||
|
static void *
|
|||
|
dp_create (long initial, long total)
|
|||
|
{
|
|||
|
struct dot_progress *dp = xmalloc (sizeof (struct dot_progress));
|
|||
|
|
|||
|
memset (dp, 0, sizeof (*dp));
|
|||
|
|
|||
|
dp->initial_length = initial;
|
|||
|
dp->total_length = total;
|
|||
|
dp->timer = wtimer_new ();
|
|||
|
|
|||
|
if (dp->initial_length)
|
|||
|
{
|
|||
|
int dot_bytes = opt.dot_bytes;
|
|||
|
long row_bytes = opt.dot_bytes * opt.dots_in_line;
|
|||
|
|
|||
|
int remainder = (int) (dp->initial_length % row_bytes);
|
|||
|
long skipped = dp->initial_length - remainder;
|
|||
|
|
|||
|
if (skipped)
|
|||
|
{
|
|||
|
logputs (LOG_VERBOSE, "\n "); /* leave spacing untranslated */
|
|||
|
logprintf (LOG_VERBOSE, _("[ skipping %dK ]"),
|
|||
|
(int) (skipped / 1024));
|
|||
|
}
|
|||
|
|
|||
|
logprintf (LOG_VERBOSE, "\n%5ldK", skipped / 1024);
|
|||
|
for (; remainder >= dot_bytes; remainder -= dot_bytes)
|
|||
|
{
|
|||
|
if (dp->dots % opt.dot_spacing == 0)
|
|||
|
logputs (LOG_VERBOSE, " ");
|
|||
|
logputs (LOG_VERBOSE, ",");
|
|||
|
++dp->dots;
|
|||
|
}
|
|||
|
assert (dp->dots < opt.dots_in_line);
|
|||
|
|
|||
|
dp->accumulated = remainder;
|
|||
|
dp->rows = skipped / row_bytes;
|
|||
|
}
|
|||
|
|
|||
|
return dp;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
print_percentage (long bytes, long expected)
|
|||
|
{
|
|||
|
int percentage = (int)(100.0 * bytes / expected);
|
|||
|
logprintf (LOG_VERBOSE, "%3d%%", percentage);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
print_elapsed (struct dot_progress *dp, long bytes)
|
|||
|
{
|
|||
|
long timer_value = wtimer_elapsed (dp->timer);
|
|||
|
logprintf (LOG_VERBOSE, " @ %s",
|
|||
|
rate (bytes, timer_value - dp->last_timer_value, 1));
|
|||
|
dp->last_timer_value = timer_value;
|
|||
|
}
|
|||
|
|
|||
|
/* Dot-progress backend for progress_update. */
|
|||
|
|
|||
|
static void
|
|||
|
dp_update (void *progress, long howmuch)
|
|||
|
{
|
|||
|
struct dot_progress *dp = progress;
|
|||
|
int dot_bytes = opt.dot_bytes;
|
|||
|
long row_bytes = opt.dot_bytes * opt.dots_in_line;
|
|||
|
|
|||
|
log_set_flush (0);
|
|||
|
|
|||
|
dp->accumulated += howmuch;
|
|||
|
for (; dp->accumulated >= dot_bytes; dp->accumulated -= dot_bytes)
|
|||
|
{
|
|||
|
if (dp->dots == 0)
|
|||
|
logprintf (LOG_VERBOSE, "\n%5ldK", dp->rows * row_bytes / 1024);
|
|||
|
|
|||
|
if (dp->dots % opt.dot_spacing == 0)
|
|||
|
logputs (LOG_VERBOSE, " ");
|
|||
|
logputs (LOG_VERBOSE, ".");
|
|||
|
|
|||
|
++dp->dots;
|
|||
|
if (dp->dots >= opt.dots_in_line)
|
|||
|
{
|
|||
|
++dp->rows;
|
|||
|
dp->dots = 0;
|
|||
|
|
|||
|
if (dp->total_length)
|
|||
|
print_percentage (dp->rows * row_bytes, dp->total_length);
|
|||
|
|
|||
|
print_elapsed (dp, row_bytes - (dp->initial_length % row_bytes));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
log_set_flush (1);
|
|||
|
}
|
|||
|
|
|||
|
/* Dot-progress backend for progress_finish. */
|
|||
|
|
|||
|
static void
|
|||
|
dp_finish (void *progress)
|
|||
|
{
|
|||
|
struct dot_progress *dp = progress;
|
|||
|
int dot_bytes = opt.dot_bytes;
|
|||
|
long row_bytes = opt.dot_bytes * opt.dots_in_line;
|
|||
|
int i;
|
|||
|
|
|||
|
log_set_flush (0);
|
|||
|
|
|||
|
for (i = dp->dots; i < opt.dots_in_line; i++)
|
|||
|
{
|
|||
|
if (i % opt.dot_spacing == 0)
|
|||
|
logputs (LOG_VERBOSE, " ");
|
|||
|
logputs (LOG_VERBOSE, " ");
|
|||
|
}
|
|||
|
if (dp->total_length)
|
|||
|
{
|
|||
|
print_percentage (dp->rows * row_bytes
|
|||
|
+ dp->dots * dot_bytes
|
|||
|
+ dp->accumulated,
|
|||
|
dp->total_length);
|
|||
|
}
|
|||
|
|
|||
|
print_elapsed (dp, dp->dots * dot_bytes
|
|||
|
+ dp->accumulated
|
|||
|
- dp->initial_length % row_bytes);
|
|||
|
logputs (LOG_VERBOSE, "\n\n");
|
|||
|
|
|||
|
log_set_flush (0);
|
|||
|
|
|||
|
wtimer_delete (dp->timer);
|
|||
|
xfree (dp);
|
|||
|
}
|
|||
|
|
|||
|
/* This function interprets the progress "parameters". For example,
|
|||
|
if Wget is invoked with --progress=bar:mega, it will set the
|
|||
|
"dot-style" to "mega". Valid styles are default, binary, mega, and
|
|||
|
giga. */
|
|||
|
|
|||
|
static void
|
|||
|
dp_set_params (const char *params)
|
|||
|
{
|
|||
|
if (!params)
|
|||
|
return;
|
|||
|
|
|||
|
/* We use this to set the retrieval style. */
|
|||
|
if (!strcasecmp (params, "default"))
|
|||
|
{
|
|||
|
/* Default style: 1K dots, 10 dots in a cluster, 50 dots in a
|
|||
|
line. */
|
|||
|
opt.dot_bytes = 1024;
|
|||
|
opt.dot_spacing = 10;
|
|||
|
opt.dots_in_line = 50;
|
|||
|
}
|
|||
|
else if (!strcasecmp (params, "binary"))
|
|||
|
{
|
|||
|
/* "Binary" retrieval: 8K dots, 16 dots in a cluster, 48 dots
|
|||
|
(384K) in a line. */
|
|||
|
opt.dot_bytes = 8192;
|
|||
|
opt.dot_spacing = 16;
|
|||
|
opt.dots_in_line = 48;
|
|||
|
}
|
|||
|
else if (!strcasecmp (params, "mega"))
|
|||
|
{
|
|||
|
/* "Mega" retrieval, for retrieving very long files; each dot is
|
|||
|
64K, 8 dots in a cluster, 6 clusters (3M) in a line. */
|
|||
|
opt.dot_bytes = 65536L;
|
|||
|
opt.dot_spacing = 8;
|
|||
|
opt.dots_in_line = 48;
|
|||
|
}
|
|||
|
else if (!strcasecmp (params, "giga"))
|
|||
|
{
|
|||
|
/* "Giga" retrieval, for retrieving very very *very* long files;
|
|||
|
each dot is 1M, 8 dots in a cluster, 4 clusters (32M) in a
|
|||
|
line. */
|
|||
|
opt.dot_bytes = (1L << 20);
|
|||
|
opt.dot_spacing = 8;
|
|||
|
opt.dots_in_line = 32;
|
|||
|
}
|
|||
|
else
|
|||
|
fprintf (stderr,
|
|||
|
_("Invalid dot style specification `%s'; leaving unchanged.\n"),
|
|||
|
params);
|
|||
|
}
|
|||
|
|
|||
|
/* "Thermometer" (bar) progress. */
|
|||
|
|
|||
|
/* Assumed screen width if we can't find the real value. */
|
|||
|
#define DEFAULT_SCREEN_WIDTH 80
|
|||
|
|
|||
|
/* Minimum screen width we'll try to work with. If this is too small,
|
|||
|
create_image will overflow the buffer. */
|
|||
|
#define MINIMUM_SCREEN_WIDTH 45
|
|||
|
|
|||
|
static int screen_width = DEFAULT_SCREEN_WIDTH;
|
|||
|
|
|||
|
struct bar_progress {
|
|||
|
long initial_length; /* how many bytes have been downloaded
|
|||
|
previously. */
|
|||
|
long total_length; /* expected total byte count when the
|
|||
|
download finishes */
|
|||
|
long count; /* bytes downloaded so far */
|
|||
|
|
|||
|
struct wget_timer *timer; /* timer used to measure the download
|
|||
|
rates. */
|
|||
|
long last_update; /* time of the last screen update. */
|
|||
|
|
|||
|
int width; /* screen width at the time the
|
|||
|
progress gauge was created. */
|
|||
|
char *buffer; /* buffer where the bar "image" is
|
|||
|
stored. */
|
|||
|
};
|
|||
|
|
|||
|
static void create_image PARAMS ((struct bar_progress *, long));
|
|||
|
static void display_image PARAMS ((char *));
|
|||
|
|
|||
|
static void *
|
|||
|
bar_create (long initial, long total)
|
|||
|
{
|
|||
|
struct bar_progress *bp = xmalloc (sizeof (struct bar_progress));
|
|||
|
|
|||
|
memset (bp, 0, sizeof (*bp));
|
|||
|
|
|||
|
bp->initial_length = initial;
|
|||
|
bp->total_length = total;
|
|||
|
bp->timer = wtimer_new ();
|
|||
|
bp->width = screen_width;
|
|||
|
bp->buffer = xmalloc (bp->width + 1);
|
|||
|
|
|||
|
logputs (LOG_VERBOSE, "\n");
|
|||
|
|
|||
|
create_image (bp, 0);
|
|||
|
display_image (bp->buffer);
|
|||
|
|
|||
|
return bp;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
bar_update (void *progress, long howmuch)
|
|||
|
{
|
|||
|
struct bar_progress *bp = progress;
|
|||
|
int force_update = 0;
|
|||
|
long dltime = wtimer_elapsed (bp->timer);
|
|||
|
|
|||
|
bp->count += howmuch;
|
|||
|
|
|||
|
if (screen_width != bp->width)
|
|||
|
{
|
|||
|
bp->width = screen_width;
|
|||
|
bp->buffer = xrealloc (bp->buffer, bp->width + 1);
|
|||
|
}
|
|||
|
|
|||
|
if (dltime - bp->last_update < 200 && !force_update)
|
|||
|
/* Don't update more often than every half a second. */
|
|||
|
return;
|
|||
|
|
|||
|
bp->last_update = dltime;
|
|||
|
|
|||
|
create_image (bp, dltime);
|
|||
|
display_image (bp->buffer);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
bar_finish (void *progress)
|
|||
|
{
|
|||
|
struct bar_progress *bp = progress;
|
|||
|
|
|||
|
create_image (bp, wtimer_elapsed (bp->timer));
|
|||
|
display_image (bp->buffer);
|
|||
|
|
|||
|
logputs (LOG_VERBOSE, "\n\n");
|
|||
|
|
|||
|
xfree (bp->buffer);
|
|||
|
wtimer_delete (bp->timer);
|
|||
|
xfree (bp);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
create_image (struct bar_progress *bp, long dltime)
|
|||
|
{
|
|||
|
char *p = bp->buffer;
|
|||
|
long size = bp->initial_length + bp->count;
|
|||
|
|
|||
|
/* The progress bar should look like this:
|
|||
|
xxx% |=======> | xx KB/s nnnnn ETA: 00:00
|
|||
|
|
|||
|
Calculate its geometry:
|
|||
|
|
|||
|
"xxx% " - percentage - 5 chars
|
|||
|
"| ... | " - progress bar decorations - 3 chars
|
|||
|
"1234.56 K/s " - dl rate - 12 chars
|
|||
|
"nnnn " - downloaded bytes - 11 chars
|
|||
|
"ETA: xx:xx:xx" - ETA - 13 chars
|
|||
|
|
|||
|
"=====>..." - progress bar content - the rest
|
|||
|
*/
|
|||
|
int progress_len = screen_width - (5 + 3 + 12 + 11 + 13);
|
|||
|
|
|||
|
if (progress_len < 7)
|
|||
|
progress_len = 0;
|
|||
|
|
|||
|
/* "xxx% " */
|
|||
|
if (bp->total_length > 0)
|
|||
|
{
|
|||
|
int percentage = (int)(100.0 * size / bp->total_length);
|
|||
|
|
|||
|
assert (percentage <= 100);
|
|||
|
|
|||
|
sprintf (p, "%3d%% ", percentage);
|
|||
|
p += 5;
|
|||
|
}
|
|||
|
|
|||
|
/* The progress bar: "|====> | " */
|
|||
|
if (progress_len && bp->total_length > 0)
|
|||
|
{
|
|||
|
double fraction = (double)size / bp->total_length;
|
|||
|
int dlsz = (int)(fraction * progress_len);
|
|||
|
char *begin;
|
|||
|
|
|||
|
assert (dlsz <= progress_len);
|
|||
|
|
|||
|
*p++ = '|';
|
|||
|
begin = p;
|
|||
|
|
|||
|
if (dlsz > 0)
|
|||
|
{
|
|||
|
/* Draw dlsz-1 '=' chars and one arrow char. */
|
|||
|
while (dlsz-- > 1)
|
|||
|
*p++ = '=';
|
|||
|
*p++ = '>';
|
|||
|
}
|
|||
|
|
|||
|
while (p - begin < progress_len)
|
|||
|
*p++ = ' ';
|
|||
|
|
|||
|
*p++ = '|';
|
|||
|
*p++ = ' ';
|
|||
|
}
|
|||
|
|
|||
|
/* "2.3 KB/s " */
|
|||
|
if (dltime && bp->count)
|
|||
|
{
|
|||
|
char *rt = rate (bp->count, dltime, 1);
|
|||
|
strcpy (p, rt);
|
|||
|
p += strlen (p);
|
|||
|
*p++ = ' ';
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
strcpy (p, "----.-- KB/s ");
|
|||
|
p += 13;
|
|||
|
}
|
|||
|
|
|||
|
/* "12376 " */
|
|||
|
sprintf (p, _("%ld "), size);
|
|||
|
p += strlen (p);
|
|||
|
|
|||
|
/* "ETA: xx:xx:xx" */
|
|||
|
if (bp->total_length > 0 && bp->count > 0)
|
|||
|
{
|
|||
|
int eta, eta_hrs, eta_min, eta_sec;
|
|||
|
double tm_sofar = (double)dltime / 1000;
|
|||
|
long bytes_remaining = bp->total_length - size;
|
|||
|
|
|||
|
eta = (int) (tm_sofar * bytes_remaining / bp->count);
|
|||
|
|
|||
|
eta_hrs = eta / 3600, eta %= 3600;
|
|||
|
eta_min = eta / 60, eta %= 60;
|
|||
|
eta_sec = eta;
|
|||
|
|
|||
|
/*printf ("\neta: %d, %d %d %d\n", eta, eta_hrs, eta_min, eta_sec);*/
|
|||
|
/*printf ("\n%ld %f %ld %ld\n", dltime, tm_sofar, bytes_remaining, bp->count);*/
|
|||
|
|
|||
|
*p++ = 'E';
|
|||
|
*p++ = 'T';
|
|||
|
*p++ = 'A';
|
|||
|
*p++ = ':';
|
|||
|
*p++ = ' ';
|
|||
|
|
|||
|
if (eta_hrs > 99)
|
|||
|
/* Bogus value, for whatever reason. We must avoid overflow. */
|
|||
|
sprintf (p, "--:--");
|
|||
|
else if (eta_hrs > 0)
|
|||
|
sprintf (p, "%d:%02d:%02d", eta_hrs, eta_min, eta_sec);
|
|||
|
else
|
|||
|
sprintf (p, "%02d:%02d", eta_min, eta_sec);
|
|||
|
p += strlen (p);
|
|||
|
}
|
|||
|
else if (bp->total_length > 0)
|
|||
|
{
|
|||
|
strcpy (p, "ETA: --:--");
|
|||
|
p += 10;
|
|||
|
}
|
|||
|
|
|||
|
assert (p - bp->buffer <= screen_width);
|
|||
|
|
|||
|
while (p < bp->buffer + screen_width)
|
|||
|
*p++ = ' ';
|
|||
|
*p = '\0';
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
display_image (char *buf)
|
|||
|
{
|
|||
|
int len = strlen (buf);
|
|||
|
char *del_buf = alloca (len + 1);
|
|||
|
|
|||
|
logputs (LOG_VERBOSE, buf);
|
|||
|
|
|||
|
memset (del_buf, '\b', len);
|
|||
|
del_buf[len] = '\0';
|
|||
|
|
|||
|
logputs (LOG_VERBOSE, del_buf);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
bar_set_params (const char *ignored)
|
|||
|
{
|
|||
|
int sw = determine_screen_width ();
|
|||
|
if (sw && sw >= MINIMUM_SCREEN_WIDTH)
|
|||
|
screen_width = sw;
|
|||
|
}
|
|||
|
|
|||
|
RETSIGTYPE
|
|||
|
progress_handle_sigwinch (int sig)
|
|||
|
{
|
|||
|
int sw = determine_screen_width ();
|
|||
|
if (sw && sw >= MINIMUM_SCREEN_WIDTH)
|
|||
|
screen_width = sw;
|
|||
|
}
|