mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2024-09-29 04:25:55 -04:00
resync with recent trunk changes, contenttype merge and final bits of
handling shoutcast DSP client. svn path=/icecast/branches/kh/icecast/; revision=8233
This commit is contained in:
parent
4b94e72ca3
commit
7afbc8a5f8
2
AUTHORS
2
AUTHORS
@ -1,4 +1,4 @@
|
|||||||
Jack Moffitt <jack@icecast.org>
|
Jack Moffitt <jack@icecast.org>
|
||||||
Michael Smith <msmith@icecast.org>
|
Michael Smith <msmith@icecast.org>
|
||||||
oddsock <oddsock@oddsock.org>
|
oddsock <oddsock@xiph.org>
|
||||||
Karl Heyes <karl@xiph.org>
|
Karl Heyes <karl@xiph.org>
|
||||||
|
45
README
45
README
@ -1,8 +1,41 @@
|
|||||||
Icecast2 Beta 1.
|
icecast 2.x - README
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
This is an beta release. Not all functionality is fully implemented, though
|
Icecast is a streaming media server which currently supports Ogg
|
||||||
we believe it to be very stable for all the currently available features.
|
Vorbis and MP3 audio streams. It can be used to create an Internet
|
||||||
|
radio station or a privately running jukebox and many things in
|
||||||
|
between. It is very versatile in that new formats can be added
|
||||||
|
relatively easily and supports open standards for commuincation and
|
||||||
|
interaction.
|
||||||
|
|
||||||
|
Icecast is distributed under the GNU GPL, version 2. A copy of this
|
||||||
|
license is included with this software in the COPYING file.
|
||||||
|
|
||||||
|
Prerequisites
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
icecast requires the following packages :
|
||||||
|
|
||||||
|
* libxml2 - http://xmlsoft.org/downloads.html
|
||||||
|
* libxslt - http://xmlsoft.org/XSLT/downloads.html
|
||||||
|
* curl - http://curl.haxx.se/download.html (>= version 7.10 required)
|
||||||
|
NOTE: icecast may be compiled without curl, however this will
|
||||||
|
disable all Directory server interaction (YP).
|
||||||
|
* ogg/vorbis - http://www.vorbis.com/files (>= version 1.0 required)
|
||||||
|
|
||||||
|
A Note About RPMS
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
This section only applies to you if your operating system uses RPMS.
|
||||||
|
|
||||||
|
In order to build icecast, you will need to install the "devel" RPM
|
||||||
|
packages for each of the prerequisite packages in addition to the
|
||||||
|
normal RPMS for each package.
|
||||||
|
|
||||||
|
please check the websites for each of the prerequisite packages for
|
||||||
|
appropriate download links for RPMS.
|
||||||
|
|
||||||
|
|
||||||
|
Build/Install
|
||||||
|
---------------------------------------------------------------------
|
||||||
To build icecast on a Unix platform, perform the following :
|
To build icecast on a Unix platform, perform the following :
|
||||||
|
|
||||||
Run
|
Run
|
||||||
@ -12,9 +45,11 @@ Run
|
|||||||
|
|
||||||
To build and install this release.
|
To build and install this release.
|
||||||
|
|
||||||
A sample config file will be placed in /usr/local/etc (on UNIX) or in the current working directory (on Win32) and is called icecast.xml
|
A sample config file will be placed in /usr/local/etc (on UNIX) or in
|
||||||
|
the current working directory (on Win32) and is called icecast.xml
|
||||||
|
|
||||||
Documentation for icecast is available in the doc directory, by viewing doc/icecast2_TOC.html in a browser.
|
Documentation for icecast is available in the doc directory, by
|
||||||
|
viewing doc/index.html in a browser.
|
||||||
|
|
||||||
Please email us at icecast@xiph.org or icecast-dev@xiph.org, or come and see
|
Please email us at icecast@xiph.org or icecast-dev@xiph.org, or come and see
|
||||||
us at irc.freenode.net, channel #icecast, if you have any troubles.
|
us at irc.freenode.net, channel #icecast, if you have any troubles.
|
||||||
|
46
TODO
46
TODO
@ -1,33 +1,13 @@
|
|||||||
2.0 CRITICAL - These are the things without which 2.0 cannot be released
|
|
||||||
____________
|
|
||||||
|
|
||||||
- Should icecast automatically (i.e. without needing -c) look for the config
|
|
||||||
file in /etc/icecast.xml or something?
|
|
||||||
|
|
||||||
- libshout 2.0 and ices 2.0 releases, also an ices 0.x release that works with
|
|
||||||
this. Without source clients, icecast isn't much use...
|
|
||||||
|
|
||||||
- integrate/include all the documentation done by external groups.
|
|
||||||
|
|
||||||
- generally we don't do proper checking for the correct versions of various
|
|
||||||
libraries (this is probably more of an issue with ices2, but it also affects
|
|
||||||
icecast)
|
|
||||||
|
|
||||||
BUGS
|
BUGS
|
||||||
----
|
----
|
||||||
- stats get off? this needs testing more testing.
|
|
||||||
|
|
||||||
- some stuff (like 'genre') isn't making it into the stats dump
|
|
||||||
|
|
||||||
- logging - bytes send and time listening may both be broken?
|
- logging - bytes send and time listening may both be broken?
|
||||||
|
|
||||||
- slave servers don't work. relay user is not respected by the source (only
|
|
||||||
admin can read /admin/streamlist), and the slave can't parse the xml result
|
|
||||||
of streamlist anyway (it expects a simple mountpoint per line)
|
|
||||||
|
|
||||||
FEATURES
|
FEATURES
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
- Should icecast automatically (i.e. without needing -c) look for the config
|
||||||
|
file in /etc/icecast.xml or something?
|
||||||
|
|
||||||
- pull out vorbis comments. and send to stats. This seems to be being
|
- pull out vorbis comments. and send to stats. This seems to be being
|
||||||
done, but it isn't working right.
|
done, but it isn't working right.
|
||||||
|
|
||||||
@ -45,41 +25,21 @@ FEATURES
|
|||||||
- allow using get_predata() stuff to send an "intro" to any newly-connected
|
- allow using get_predata() stuff to send an "intro" to any newly-connected
|
||||||
user?
|
user?
|
||||||
|
|
||||||
- stats to list currently connected clients: ip and hostname
|
|
||||||
|
|
||||||
- stream switching (drop clients to another stream on disconnect of source)
|
|
||||||
- a) fallbacks from named location to new mountpoint
|
|
||||||
- OR b) fallbacks for connected clients to new mountpoint (so newly-connecting
|
|
||||||
clients just get a 404 on the old path)
|
|
||||||
- OR c) combination - first one, plus generic alias ability?
|
|
||||||
|
|
||||||
- /admin/* for all admin functionality
|
|
||||||
- configuring fallbacks
|
|
||||||
- mp3 metadata injection
|
|
||||||
- remote shutdown?
|
|
||||||
|
|
||||||
- general registerable url-handlers in connection.c rather than hard-coded list
|
- general registerable url-handlers in connection.c rather than hard-coded list
|
||||||
(already getting unmaintainable)
|
(already getting unmaintainable)
|
||||||
|
|
||||||
- httpp - split out query string for further processing
|
- httpp - split out query string for further processing
|
||||||
|
|
||||||
- option to use ipv6 (equiv to using <bind-address>::</bindaddress>, I think.
|
|
||||||
|
|
||||||
- abstract all admin functionality to a set of commands, and command handlers.
|
- abstract all admin functionality to a set of commands, and command handlers.
|
||||||
Make /admin/* just parse according to a set of rules, and dispatch generic
|
Make /admin/* just parse according to a set of rules, and dispatch generic
|
||||||
commands through that.
|
commands through that.
|
||||||
Use this for alternative admin interfaces (GUI? telnet interface?)
|
Use this for alternative admin interfaces (GUI? telnet interface?)
|
||||||
|
|
||||||
- listener authentication (per mountpoint?)
|
|
||||||
|
|
||||||
- all timer-based functionality (yp updates, slave/relay checks) should have a
|
- all timer-based functionality (yp updates, slave/relay checks) should have a
|
||||||
single timer thread which dispatches events through the normal event
|
single timer thread which dispatches events through the normal event
|
||||||
mechanism (to worker threads from the main pool). This will reduce the
|
mechanism (to worker threads from the main pool). This will reduce the
|
||||||
extraneous thread count.
|
extraneous thread count.
|
||||||
|
|
||||||
- atomic admin function to: set fallback from A->B, remove A, move mountpoint
|
|
||||||
B to A. Needs forced-source removal first.
|
|
||||||
|
|
||||||
- race condition between avl_tree_unlock(pending_tree) and
|
- race condition between avl_tree_unlock(pending_tree) and
|
||||||
thread_cond_wait(&fserv_cond) in fserv.c, it's a pain to fix but should be.
|
thread_cond_wait(&fserv_cond) in fserv.c, it's a pain to fix but should be.
|
||||||
|
|
||||||
|
@ -138,6 +138,12 @@
|
|||||||
|
|
||||||
<fileserve>1</fileserve>
|
<fileserve>1</fileserve>
|
||||||
|
|
||||||
|
<!-- set the mountpoint for a shoutcast source to use, the default if not
|
||||||
|
specified is /stream but you can change it here if an alternative is
|
||||||
|
wanted or an extension is required
|
||||||
|
<shoutcast-mount>/live.nsv</shoutcast-mount>
|
||||||
|
-->
|
||||||
|
|
||||||
<paths>
|
<paths>
|
||||||
<!-- basedir is only used if chroot is enabled -->
|
<!-- basedir is only used if chroot is enabled -->
|
||||||
<basedir>@pkgdatadir@</basedir>
|
<basedir>@pkgdatadir@</basedir>
|
||||||
|
@ -74,7 +74,7 @@ XIPH_PATH_XSLT
|
|||||||
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$XSLT_CFLAGS])
|
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$XSLT_CFLAGS])
|
||||||
XIPH_VAR_PREPEND([XIPH_LIBS],[$XSLT_LIBS])
|
XIPH_VAR_PREPEND([XIPH_LIBS],[$XSLT_LIBS])
|
||||||
|
|
||||||
XIPH_PATH_VORBIS(, AC_MSG_ERROR([must have Ogg Vorbis v1.0 installed!]))
|
XIPH_PATH_VORBIS(, AC_MSG_ERROR([must have Ogg Vorbis v1.0 or above installed!]))
|
||||||
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$VORBIS_CFLAGS])
|
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$VORBIS_CFLAGS])
|
||||||
XIPH_VAR_PREPEND([XIPH_LIBS],[$VORBIS_LIBS])
|
XIPH_VAR_PREPEND([XIPH_LIBS],[$VORBIS_LIBS])
|
||||||
XIPH_VAR_APPEND([XIPH_LDFLAGS],[$VORBIS_LDFLAGS])
|
XIPH_VAR_APPEND([XIPH_LDFLAGS],[$VORBIS_LDFLAGS])
|
||||||
|
BIN
doc/icecast2.chm
BIN
doc/icecast2.chm
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
Name: icecast
|
Name: icecast
|
||||||
Version: 2.0.0
|
Version: 2.1.0-kh
|
||||||
Release: 1
|
Release: 1
|
||||||
Summary: Xiph Streaming media server that supports multiple audio formats.
|
Summary: Xiph Streaming media server that supports multiple audio formats.
|
||||||
|
|
||||||
|
93
src/admin.c
93
src/admin.c
@ -148,14 +148,14 @@ int admin_get_command(char *command)
|
|||||||
return COMMAND_TRANSFORMED_KILL_CLIENT;
|
return COMMAND_TRANSFORMED_KILL_CLIENT;
|
||||||
else if(!strcmp(command, KILLSOURCE_RAW_REQUEST))
|
else if(!strcmp(command, KILLSOURCE_RAW_REQUEST))
|
||||||
return COMMAND_RAW_KILL_SOURCE;
|
return COMMAND_RAW_KILL_SOURCE;
|
||||||
else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST))
|
|
||||||
return COMMAND_RAW_MANAGEAUTH;
|
|
||||||
else if(!strcmp(command, BUILDM3U_RAW_REQUEST))
|
|
||||||
return COMMAND_BUILDM3U;
|
|
||||||
else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST))
|
|
||||||
return COMMAND_TRANSFORMED_MANAGEAUTH;
|
|
||||||
else if(!strcmp(command, KILLSOURCE_TRANSFORMED_REQUEST))
|
else if(!strcmp(command, KILLSOURCE_TRANSFORMED_REQUEST))
|
||||||
return COMMAND_TRANSFORMED_KILL_SOURCE;
|
return COMMAND_TRANSFORMED_KILL_SOURCE;
|
||||||
|
else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST))
|
||||||
|
return COMMAND_RAW_MANAGEAUTH;
|
||||||
|
else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST))
|
||||||
|
return COMMAND_TRANSFORMED_MANAGEAUTH;
|
||||||
|
else if(!strcmp(command, BUILDM3U_RAW_REQUEST))
|
||||||
|
return COMMAND_BUILDM3U;
|
||||||
else if(!strcmp(command, ADM_FUNCTION_RAW_REQUEST))
|
else if(!strcmp(command, ADM_FUNCTION_RAW_REQUEST))
|
||||||
return COMMAND_RAW_ADMIN_FUNCTION;
|
return COMMAND_RAW_ADMIN_FUNCTION;
|
||||||
else if(!strcmp(command, ADM_FUNCTION_TRANSFORMED_REQUEST))
|
else if(!strcmp(command, ADM_FUNCTION_TRANSFORMED_REQUEST))
|
||||||
@ -236,8 +236,8 @@ xmlDocPtr admin_build_sourcelist(char *current_source)
|
|||||||
snprintf (buf, sizeof(buf), "%lu",
|
snprintf (buf, sizeof(buf), "%lu",
|
||||||
(unsigned long)(now - source->con->con_time));
|
(unsigned long)(now - source->con->con_time));
|
||||||
xmlNewChild (srcnode, NULL, "Connected", buf);
|
xmlNewChild (srcnode, NULL, "Connected", buf);
|
||||||
xmlNewChild (srcnode, NULL, "Format",
|
xmlNewChild (srcnode, NULL, "content-type",
|
||||||
source->format->format_description);
|
source->format->contenttype);
|
||||||
if (source->authenticator)
|
if (source->authenticator)
|
||||||
{
|
{
|
||||||
xmlNewChild(srcnode, NULL, "authenticator",
|
xmlNewChild(srcnode, NULL, "authenticator",
|
||||||
@ -321,9 +321,18 @@ void admin_handle_request(client_t *client, char *uri)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {
|
if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {
|
||||||
ice_config_t *config = config_get_config ();
|
|
||||||
|
|
||||||
|
ice_config_t *config;
|
||||||
|
char *pass = httpp_get_query_param (client->parser, "pass");
|
||||||
|
if (pass == NULL)
|
||||||
|
{
|
||||||
|
client_send_400 (client, "missing pass parameter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
config = config_get_config ();
|
||||||
httpp_set_query_param (client->parser, "mount", config->shoutcast_mount);
|
httpp_set_query_param (client->parser, "mount", config->shoutcast_mount);
|
||||||
|
httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
|
||||||
|
httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
|
||||||
config_release_config ();
|
config_release_config ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,9 +345,18 @@ void admin_handle_request(client_t *client, char *uri)
|
|||||||
client->authenticated = 1;
|
client->authenticated = 1;
|
||||||
}
|
}
|
||||||
/* This is a mount request, but admin user is allowed */
|
/* This is a mount request, but admin user is allowed */
|
||||||
if (client->authenticated != 1) {
|
if (client->authenticated != 1)
|
||||||
if (connection_check_admin_pass(client->parser))
|
{
|
||||||
client->authenticated = 1;
|
if (connection_check_admin_pass(client->parser) == 0)
|
||||||
|
{
|
||||||
|
if (connection_check_source_pass(client->parser, mount) == 0)
|
||||||
|
{
|
||||||
|
INFO1("Bad or missing password on mount modification admin "
|
||||||
|
"request (command: %s)", command_string);
|
||||||
|
client_send_401(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
avl_tree_rlock(global.source_tree);
|
avl_tree_rlock(global.source_tree);
|
||||||
@ -352,31 +370,26 @@ void admin_handle_request(client_t *client, char *uri)
|
|||||||
client_send_400 (client, "Source does not exist");
|
client_send_400 (client, "Source does not exist");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
INFO2("Received admin command %s on mount \"%s\"",
|
|
||||||
command_string, mount);
|
|
||||||
if (source->shoutcast_compat == 0)
|
|
||||||
{
|
{
|
||||||
if (source->running == 0 && source->on_demand == 0)
|
if (source->running == 0 && source->on_demand == 0)
|
||||||
{
|
{
|
||||||
|
avl_tree_unlock (global.source_tree);
|
||||||
INFO2("Received admin command %s on unavailable mount \"%s\"",
|
INFO2("Received admin command %s on unavailable mount \"%s\"",
|
||||||
command_string, mount);
|
command_string, mount);
|
||||||
avl_tree_unlock (global.source_tree);
|
|
||||||
client_send_400 (client, "Source is not available");
|
client_send_400 (client, "Source is not available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (client->authenticated != 1)
|
if (command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
|
||||||
|
source->shoutcast_compat == 0)
|
||||||
{
|
{
|
||||||
if (connection_check_source_pass(client->parser, mount) == 0)
|
avl_tree_unlock (global.source_tree);
|
||||||
{
|
ERROR0 ("illegal change of metadata on non-shoutcast "
|
||||||
INFO1("Bad or missing password on mount modification admin "
|
"compatible stream");
|
||||||
"request (command: %s)", command_string);
|
client_send_400 (client, "illegal metadata call");
|
||||||
avl_tree_unlock(global.source_tree);
|
|
||||||
client_send_401(client);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
INFO2("Received admin command %s on mount \"%s\"",
|
||||||
}
|
command_string, mount);
|
||||||
admin_handle_mount_request (client, source, command);
|
admin_handle_mount_request (client, source, command);
|
||||||
avl_tree_unlock(global.source_tree);
|
avl_tree_unlock(global.source_tree);
|
||||||
}
|
}
|
||||||
@ -940,27 +953,11 @@ static void command_shoutcast_metadata(client_t *client, source_t *source)
|
|||||||
{
|
{
|
||||||
char *action;
|
char *action;
|
||||||
char *value;
|
char *value;
|
||||||
char *source_pass;
|
|
||||||
char *config_source_pass;
|
|
||||||
ice_config_t *config;
|
|
||||||
|
|
||||||
DEBUG0("Got shoutcast metadata update request");
|
DEBUG0("Got shoutcast metadata update request");
|
||||||
|
|
||||||
COMMAND_REQUIRE(client, "mode", action);
|
COMMAND_REQUIRE(client, "mode", action);
|
||||||
COMMAND_REQUIRE(client, "song", value);
|
COMMAND_REQUIRE(client, "song", value);
|
||||||
COMMAND_REQUIRE(client, "pass", source_pass);
|
|
||||||
|
|
||||||
config = config_get_config();
|
|
||||||
config_source_pass = strdup(config->source_password);
|
|
||||||
config_release_config();
|
|
||||||
|
|
||||||
if ((source->format->type != FORMAT_TYPE_MP3) &&
|
|
||||||
(source->format->type != FORMAT_TYPE_NSV))
|
|
||||||
{
|
|
||||||
thread_mutex_unlock (&source->lock);
|
|
||||||
client_send_400 (client, "Not mp3 or NSV, cannot update metadata");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp (action, "updinfo") != 0)
|
if (strcmp (action, "updinfo") != 0)
|
||||||
{
|
{
|
||||||
@ -969,18 +966,6 @@ static void command_shoutcast_metadata(client_t *client, source_t *source)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(source_pass, config_source_pass) != 0)
|
|
||||||
{
|
|
||||||
thread_mutex_unlock (&source->lock);
|
|
||||||
ERROR0("Invalid source password specified, metadata not updated");
|
|
||||||
client_send_400 (client, "Invalid source password");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config_source_pass) {
|
|
||||||
free(config_source_pass);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source->format && source->format->set_tag)
|
if (source->format && source->format->set_tag)
|
||||||
{
|
{
|
||||||
source->format->set_tag (source->format, "title", value);
|
source->format->set_tag (source->format, "title", value);
|
||||||
|
@ -464,7 +464,7 @@ int connection_complete_source (source_t *source)
|
|||||||
{
|
{
|
||||||
WARN0("No content-type header, falling back to backwards compatibility mode "
|
WARN0("No content-type header, falling back to backwards compatibility mode "
|
||||||
"for icecast 1.x relays. Assuming content is mp3.");
|
"for icecast 1.x relays. Assuming content is mp3.");
|
||||||
format_type = FORMAT_TYPE_MP3;
|
format_type = FORMAT_TYPE_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (format_get_plugin (format_type, source) < 0)
|
if (format_get_plugin (format_type, source) < 0)
|
||||||
@ -582,13 +582,18 @@ int connection_check_admin_pass(http_parser_t *parser)
|
|||||||
ice_config_t *config = config_get_config();
|
ice_config_t *config = config_get_config();
|
||||||
char *pass = config->admin_password;
|
char *pass = config->admin_password;
|
||||||
char *user = config->admin_username;
|
char *user = config->admin_username;
|
||||||
|
char *protocol;
|
||||||
|
|
||||||
if(!pass || !user) {
|
if(!pass || !user) {
|
||||||
config_release_config();
|
config_release_config();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = _check_pass_http(parser, user, pass);
|
protocol = httpp_getvar (parser, HTTPP_VAR_PROTOCOL);
|
||||||
|
if (protocol && strcmp (protocol, "ICY") == 0)
|
||||||
|
ret = _check_pass_icy (parser, pass);
|
||||||
|
else
|
||||||
|
ret = _check_pass_http (parser, user, pass);
|
||||||
config_release_config();
|
config_release_config();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -881,9 +886,8 @@ static void _handle_get_request(connection_t *con,
|
|||||||
if (uri != passed_uri) free (uri);
|
if (uri != passed_uri) free (uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handle_shoutcast_compatible(connection_t *con, char *source_password) {
|
void _handle_shoutcast_compatible(connection_t *con, char *mount, char *source_password) {
|
||||||
char shoutcast_password[256];
|
char shoutcast_password[256];
|
||||||
char shoutcast_source[256];
|
|
||||||
char *http_compliant;
|
char *http_compliant;
|
||||||
int http_compliant_len = 0;
|
int http_compliant_len = 0;
|
||||||
char header[4096];
|
char header[4096];
|
||||||
@ -924,18 +928,14 @@ void _handle_shoutcast_compatible(connection_t *con, char *source_password) {
|
|||||||
/* Here we create a valid HTTP request based of the information
|
/* Here we create a valid HTTP request based of the information
|
||||||
that was passed in via the non-HTTP style protocol above. This
|
that was passed in via the non-HTTP style protocol above. This
|
||||||
means we can use some of our existing code to handle this case */
|
means we can use some of our existing code to handle this case */
|
||||||
memset(shoutcast_source, 0, sizeof (shoutcast_source));
|
http_compliant_len = strlen(header) + strlen(mount) + 20;
|
||||||
strcpy(shoutcast_source, "SOURCE / HTTP/1.0\r\n");
|
|
||||||
http_compliant_len = strlen(shoutcast_source) +
|
|
||||||
strlen(header) + 1;
|
|
||||||
http_compliant = (char *)calloc(1, http_compliant_len);
|
http_compliant = (char *)calloc(1, http_compliant_len);
|
||||||
sprintf(http_compliant, "%s%s", shoutcast_source,
|
snprintf (http_compliant, http_compliant_len,
|
||||||
header);
|
"SOURCE %s HTTP/1.0\r\n%s", mount, header);
|
||||||
parser = httpp_create_parser();
|
parser = httpp_create_parser();
|
||||||
httpp_initialize(parser, NULL);
|
httpp_initialize(parser, NULL);
|
||||||
if (httpp_parse(parser, http_compliant,
|
if (httpp_parse(parser, http_compliant, strlen(http_compliant))) {
|
||||||
strlen(http_compliant))) {
|
_handle_source_request(con, parser, mount, SHOUTCAST_SOURCE_AUTH);
|
||||||
_handle_source_request(con, parser, "/", SHOUTCAST_SOURCE_AUTH);
|
|
||||||
free(http_compliant);
|
free(http_compliant);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -990,10 +990,12 @@ static void *_handle_connection(void *arg)
|
|||||||
if(global.serversock[i] == con->serversock) {
|
if(global.serversock[i] == con->serversock) {
|
||||||
config = config_get_config();
|
config = config_get_config();
|
||||||
if (config->listeners[i].shoutcast_compat) {
|
if (config->listeners[i].shoutcast_compat) {
|
||||||
|
char *shoutcast_mount = strdup (config->shoutcast_mount);
|
||||||
source_password = strdup(config->source_password);
|
source_password = strdup(config->source_password);
|
||||||
config_release_config();
|
config_release_config();
|
||||||
_handle_shoutcast_compatible(con, source_password);
|
_handle_shoutcast_compatible(con, shoutcast_mount, source_password);
|
||||||
free(source_password);
|
free(source_password);
|
||||||
|
free (shoutcast_mount);
|
||||||
continue_flag = 1;
|
continue_flag = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
62
src/format.c
62
src/format.c
@ -58,75 +58,29 @@ format_type_t format_get_type(char *contenttype)
|
|||||||
return FORMAT_TYPE_OGG; /* Backwards compatibility */
|
return FORMAT_TYPE_OGG; /* Backwards compatibility */
|
||||||
else if(strcmp(contenttype, "application/ogg") == 0)
|
else if(strcmp(contenttype, "application/ogg") == 0)
|
||||||
return FORMAT_TYPE_OGG; /* Now blessed by IANA */
|
return FORMAT_TYPE_OGG; /* Now blessed by IANA */
|
||||||
else if(strcmp(contenttype, "audio/mpeg") == 0)
|
|
||||||
return FORMAT_TYPE_MP3;
|
|
||||||
else if(strcmp(contenttype, "audio/x-mpeg") == 0)
|
|
||||||
return FORMAT_TYPE_MP3;
|
|
||||||
else if(strcmp(contenttype, "video/nsv") == 0)
|
|
||||||
return FORMAT_TYPE_NSV;
|
|
||||||
else if(strcmp(contenttype, "audio/aac") == 0)
|
|
||||||
return FORMAT_TYPE_AAC;
|
|
||||||
else if(strcmp(contenttype, "audio/aacp") == 0)
|
|
||||||
return FORMAT_TYPE_AACPLUS;
|
|
||||||
else
|
else
|
||||||
return FORMAT_ERROR;
|
/* We default to the Generic format handler, which
|
||||||
}
|
can handle many more formats than just mp3 */
|
||||||
|
return FORMAT_TYPE_GENERIC;
|
||||||
const char *format_get_mimetype(format_type_t type)
|
|
||||||
{
|
|
||||||
switch(type) {
|
|
||||||
case FORMAT_TYPE_OGG:
|
|
||||||
return "application/ogg";
|
|
||||||
break;
|
|
||||||
case FORMAT_TYPE_MP3:
|
|
||||||
return "audio/mpeg";
|
|
||||||
break;
|
|
||||||
case FORMAT_TYPE_NSV:
|
|
||||||
return "video/nsv";
|
|
||||||
break;
|
|
||||||
case FORMAT_TYPE_AAC:
|
|
||||||
return "audio/aac";
|
|
||||||
break;
|
|
||||||
case FORMAT_TYPE_AACPLUS:
|
|
||||||
return "audio/aacp";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int format_get_plugin(format_type_t type, source_t *source)
|
int format_get_plugin(format_type_t type, source_t *source)
|
||||||
{
|
{
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
|
|
||||||
switch (type)
|
switch (type) {
|
||||||
{
|
|
||||||
case FORMAT_TYPE_OGG:
|
case FORMAT_TYPE_OGG:
|
||||||
ret = format_ogg_get_plugin (source);
|
ret = format_ogg_get_plugin (source);
|
||||||
break;
|
break;
|
||||||
case FORMAT_TYPE_MP3:
|
case FORMAT_TYPE_GENERIC:
|
||||||
ret = format_mp3_get_plugin (source);
|
ret = format_mp3_get_plugin (source);
|
||||||
break;
|
break;
|
||||||
case FORMAT_TYPE_NSV:
|
|
||||||
ret = format_mp3_get_plugin (source);
|
|
||||||
source->format->format_description = "NSV Video";
|
|
||||||
source->format->type = FORMAT_TYPE_NSV;
|
|
||||||
break;
|
|
||||||
case FORMAT_TYPE_AAC:
|
|
||||||
ret = format_mp3_get_plugin (source);
|
|
||||||
source->format->format_description = "AAC Audio";
|
|
||||||
source->format->type = FORMAT_TYPE_AAC;
|
|
||||||
break;
|
|
||||||
case FORMAT_TYPE_AACPLUS:
|
|
||||||
ret = format_mp3_get_plugin (source);
|
|
||||||
source->format->format_description = "AACPlus Audio";
|
|
||||||
source->format->type = FORMAT_TYPE_AACPLUS;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (ret < 0)
|
||||||
stats_event (source->mount, "content-type",
|
stats_event (source->mount, "content-type",
|
||||||
format_get_mimetype(source->format->type));
|
source->format->contenttype);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -274,7 +228,7 @@ static int format_prepare_headers (source_t *source, client_t *client)
|
|||||||
client->respcode = 200;
|
client->respcode = 200;
|
||||||
|
|
||||||
bytes = snprintf (ptr, remaining, "HTTP/1.0 200 OK\r\n"
|
bytes = snprintf (ptr, remaining, "HTTP/1.0 200 OK\r\n"
|
||||||
"Content-Type: %s\r\n", format_get_mimetype (source->format->type));
|
"Content-Type: %s\r\n", source->format->contenttype);
|
||||||
|
|
||||||
remaining -= bytes;
|
remaining -= bytes;
|
||||||
ptr += bytes;
|
ptr += bytes;
|
||||||
|
@ -29,11 +29,7 @@ typedef enum _format_type_tag
|
|||||||
{
|
{
|
||||||
FORMAT_ERROR, /* No format, source not processable */
|
FORMAT_ERROR, /* No format, source not processable */
|
||||||
FORMAT_TYPE_OGG,
|
FORMAT_TYPE_OGG,
|
||||||
FORMAT_TYPE_VORBIS,
|
FORMAT_TYPE_GENERIC
|
||||||
FORMAT_TYPE_MP3,
|
|
||||||
FORMAT_TYPE_NSV,
|
|
||||||
FORMAT_TYPE_AAC,
|
|
||||||
FORMAT_TYPE_AACPLUS
|
|
||||||
} format_type_t;
|
} format_type_t;
|
||||||
|
|
||||||
typedef struct _format_plugin_tag
|
typedef struct _format_plugin_tag
|
||||||
@ -43,7 +39,7 @@ typedef struct _format_plugin_tag
|
|||||||
/* we need to know the mount to report statistics */
|
/* we need to know the mount to report statistics */
|
||||||
char *mount;
|
char *mount;
|
||||||
|
|
||||||
char *format_description;
|
char *contenttype;
|
||||||
|
|
||||||
refbuf_t *(*get_buffer)(struct source_tag *);
|
refbuf_t *(*get_buffer)(struct source_tag *);
|
||||||
int (*write_buf_to_client)(struct source_tag *source, client_t *client);
|
int (*write_buf_to_client)(struct source_tag *source, client_t *client);
|
||||||
@ -59,7 +55,6 @@ typedef struct _format_plugin_tag
|
|||||||
} format_plugin_t;
|
} format_plugin_t;
|
||||||
|
|
||||||
format_type_t format_get_type(char *contenttype);
|
format_type_t format_get_type(char *contenttype);
|
||||||
const char *format_get_mimetype(format_type_t type);
|
|
||||||
int format_get_plugin(format_type_t type, struct source_tag *source);
|
int format_get_plugin(format_type_t type, struct source_tag *source);
|
||||||
|
|
||||||
int format_generic_write_to_client (struct source_tag *source, client_t *client);
|
int format_generic_write_to_client (struct source_tag *source, client_t *client);
|
||||||
|
@ -82,7 +82,7 @@ int format_mp3_get_plugin (source_t *source)
|
|||||||
|
|
||||||
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
|
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
|
||||||
|
|
||||||
plugin->type = FORMAT_TYPE_MP3;
|
plugin->type = FORMAT_TYPE_GENERIC;
|
||||||
plugin->get_buffer = mp3_get_no_meta;
|
plugin->get_buffer = mp3_get_no_meta;
|
||||||
plugin->write_buf_to_client = format_mp3_write_buf_to_client;
|
plugin->write_buf_to_client = format_mp3_write_buf_to_client;
|
||||||
plugin->write_buf_to_file = write_mp3_to_file;
|
plugin->write_buf_to_file = write_mp3_to_file;
|
||||||
@ -90,9 +90,14 @@ int format_mp3_get_plugin (source_t *source)
|
|||||||
plugin->free_plugin = format_mp3_free_plugin;
|
plugin->free_plugin = format_mp3_free_plugin;
|
||||||
plugin->set_tag = mp3_set_tag;
|
plugin->set_tag = mp3_set_tag;
|
||||||
plugin->prerelease = NULL;
|
plugin->prerelease = NULL;
|
||||||
plugin->format_description = "MP3 audio";
|
|
||||||
plugin->apply_settings = format_mp3_apply_settings;
|
plugin->apply_settings = format_mp3_apply_settings;
|
||||||
|
|
||||||
|
plugin->contenttype = httpp_getvar (source->parser, "content-type");
|
||||||
|
if (plugin->contenttype == NULL) {
|
||||||
|
/* We default to MP3 audio for old clients without content types */
|
||||||
|
plugin->contenttype = "audio/mpeg";
|
||||||
|
}
|
||||||
|
|
||||||
plugin->_state = state;
|
plugin->_state = state;
|
||||||
|
|
||||||
/* initial metadata needs to be blank for sending to clients and for
|
/* initial metadata needs to be blank for sending to clients and for
|
||||||
|
@ -551,7 +551,6 @@ int format_ogg_get_plugin (source_t *source)
|
|||||||
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
|
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
|
||||||
|
|
||||||
plugin->type = FORMAT_TYPE_OGG;
|
plugin->type = FORMAT_TYPE_OGG;
|
||||||
plugin->format_description = "Ogg Vorbis";
|
|
||||||
plugin->get_buffer = ogg_get_buffer;
|
plugin->get_buffer = ogg_get_buffer;
|
||||||
plugin->write_buf_to_client = write_buf_to_client;
|
plugin->write_buf_to_client = write_buf_to_client;
|
||||||
plugin->write_buf_to_file = write_ogg_to_file;
|
plugin->write_buf_to_file = write_ogg_to_file;
|
||||||
@ -559,6 +558,7 @@ int format_ogg_get_plugin (source_t *source)
|
|||||||
plugin->free_plugin = format_ogg_free_plugin;
|
plugin->free_plugin = format_ogg_free_plugin;
|
||||||
plugin->set_tag = NULL;
|
plugin->set_tag = NULL;
|
||||||
plugin->prerelease = refbuf_page_prerelease;
|
plugin->prerelease = refbuf_page_prerelease;
|
||||||
|
plugin->contenttype = "application/ogg";
|
||||||
|
|
||||||
ogg_sync_init (&state->oy);
|
ogg_sync_init (&state->oy);
|
||||||
|
|
||||||
|
@ -121,13 +121,13 @@ int format_ogg_get_plugin (source_t *source)
|
|||||||
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
|
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
|
||||||
|
|
||||||
plugin->type = FORMAT_TYPE_OGG;
|
plugin->type = FORMAT_TYPE_OGG;
|
||||||
plugin->format_description = "Ogg Vorbis";
|
|
||||||
plugin->get_buffer = vorbis_get_buffer;
|
plugin->get_buffer = vorbis_get_buffer;
|
||||||
plugin->write_buf_to_client = vorbis_write_buf_to_client;
|
plugin->write_buf_to_client = vorbis_write_buf_to_client;
|
||||||
plugin->write_buf_to_file = write_vorbis_to_file;
|
plugin->write_buf_to_file = write_vorbis_to_file;
|
||||||
plugin->create_client_data = create_vorbis_client_data;
|
plugin->create_client_data = create_vorbis_client_data;
|
||||||
plugin->free_plugin = format_vorbis_free_plugin;
|
plugin->free_plugin = format_vorbis_free_plugin;
|
||||||
plugin->set_tag = vorbis_set_tag;
|
plugin->set_tag = vorbis_set_tag;
|
||||||
|
plugin->contenttype = "application/ogg";
|
||||||
|
|
||||||
state = (vstate_t *)calloc(1, sizeof(vstate_t));
|
state = (vstate_t *)calloc(1, sizeof(vstate_t));
|
||||||
ogg_sync_init(&state->oy);
|
ogg_sync_init(&state->oy);
|
||||||
|
@ -427,14 +427,14 @@ int fserve_client_create(client_t *httpclient, char *path)
|
|||||||
bytes = sock_write(httpclient->con->sock,
|
bytes = sock_write(httpclient->con->sock,
|
||||||
"HTTP/1.1 206 Partial Content\r\n"
|
"HTTP/1.1 206 Partial Content\r\n"
|
||||||
"Date: %s\r\n"
|
"Date: %s\r\n"
|
||||||
"Content-Length: %ld\r\n"
|
"Content-Length: " FORMAT_INT64 "\r\n"
|
||||||
"Content-Range: bytes " FORMAT_INT64 \
|
"Content-Range: bytes " FORMAT_INT64 \
|
||||||
"-" FORMAT_INT64 "/" FORMAT_INT64 "\r\n"
|
"-" FORMAT_INT64 "/" FORMAT_INT64 "\r\n"
|
||||||
"Content-Type: %s\r\n\r\n",
|
"Content-Type: %s\r\n\r\n",
|
||||||
currenttime,
|
currenttime,
|
||||||
new_content_len,
|
new_content_len,
|
||||||
rangenumber,
|
rangenumber,
|
||||||
rangenumber+new_content_len,
|
endpos,
|
||||||
client->content_length,
|
client->content_length,
|
||||||
fserve_content_type(path));
|
fserve_content_type(path));
|
||||||
}
|
}
|
||||||
@ -445,7 +445,9 @@ int fserve_client_create(client_t *httpclient, char *path)
|
|||||||
httpclient->respcode = 200;
|
httpclient->respcode = 200;
|
||||||
bytes = sock_write (httpclient->con->sock,
|
bytes = sock_write (httpclient->con->sock,
|
||||||
"HTTP/1.0 200 OK\r\n"
|
"HTTP/1.0 200 OK\r\n"
|
||||||
|
"Content-Length: " FORMAT_INT64 "\r\n"
|
||||||
"Content-Type: %s\r\n\r\n",
|
"Content-Type: %s\r\n\r\n",
|
||||||
|
client->content_length,
|
||||||
fserve_content_type(path));
|
fserve_content_type(path));
|
||||||
}
|
}
|
||||||
if(bytes > 0) httpclient->con->sent_bytes = bytes;
|
if(bytes > 0) httpclient->con->sent_bytes = bytes;
|
||||||
|
@ -20,8 +20,8 @@ typedef struct _fserve_t
|
|||||||
client_t *client;
|
client_t *client;
|
||||||
|
|
||||||
FILE *file;
|
FILE *file;
|
||||||
int ready;
|
|
||||||
int64_t content_length;
|
int64_t content_length;
|
||||||
|
int ready;
|
||||||
struct _fserve_t *next;
|
struct _fserve_t *next;
|
||||||
} fserve_t;
|
} fserve_t;
|
||||||
|
|
||||||
|
@ -88,6 +88,7 @@ static void _stop_logging(void)
|
|||||||
{
|
{
|
||||||
log_close(errorlog);
|
log_close(errorlog);
|
||||||
log_close(accesslog);
|
log_close(accesslog);
|
||||||
|
log_close(playlistlog);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _initialize_subsystems(void)
|
static void _initialize_subsystems(void)
|
||||||
@ -203,7 +204,6 @@ static int _start_logging(void)
|
|||||||
strerror(errno));
|
strerror(errno));
|
||||||
_fatal_error(buf);
|
_fatal_error(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_set_level(errorlog, config->loglevel);
|
log_set_level(errorlog, config->loglevel);
|
||||||
|
|
||||||
if(strcmp(config->access_log, "-")) {
|
if(strcmp(config->access_log, "-")) {
|
||||||
|
@ -738,7 +738,6 @@ static void source_init (source_t *source)
|
|||||||
source->listeners = 0;
|
source->listeners = 0;
|
||||||
stats_event_inc (NULL, "sources");
|
stats_event_inc (NULL, "sources");
|
||||||
stats_event_inc (NULL, "source_total_connections");
|
stats_event_inc (NULL, "source_total_connections");
|
||||||
stats_event (source->mount, "type", source->format->format_description);
|
|
||||||
|
|
||||||
if (source->con)
|
if (source->con)
|
||||||
sock_set_blocking (source->con->sock, SOCK_NONBLOCK);
|
sock_set_blocking (source->con->sock, SOCK_NONBLOCK);
|
||||||
|
2
src/yp.c
2
src/yp.c
@ -512,7 +512,7 @@ static ypdata_t *create_yp_entry (source_t *source)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
/* ice-* is icecast, icy-* is shoutcast */
|
/* ice-* is icecast, icy-* is shoutcast */
|
||||||
add_yp_info (yp, "server_type", source->format->format_description, YP_SERVER_TYPE);
|
add_yp_info (yp, "server_type", source->format->contenttype, YP_SERVER_TYPE);
|
||||||
if ((s = httpp_getvar(source->parser, "ice-name"))) {
|
if ((s = httpp_getvar(source->parser, "ice-name"))) {
|
||||||
add_yp_info (yp, "server_name", s, YP_SERVER_NAME);
|
add_yp_info (yp, "server_name", s, YP_SERVER_NAME);
|
||||||
}
|
}
|
||||||
|
@ -1,442 +0,0 @@
|
|||||||
// ResizableDialog.cpp : implementation file
|
|
||||||
//
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Copyright (C) 2000 by Paolo Messina
|
|
||||||
// (ppescher@yahoo.com)
|
|
||||||
//
|
|
||||||
// Free for non-commercial use.
|
|
||||||
// You may change the code to your needs,
|
|
||||||
// provided that credits to the original
|
|
||||||
// author is given in the modified files.
|
|
||||||
//
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
#include "stdafx.h"
|
|
||||||
#include "ResizableDialog.h"
|
|
||||||
|
|
||||||
#ifdef _DEBUG
|
|
||||||
#define new DEBUG_NEW
|
|
||||||
#undef THIS_FILE
|
|
||||||
static char THIS_FILE[] = __FILE__;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
// CResizableDialog
|
|
||||||
|
|
||||||
inline void CResizableDialog::Construct()
|
|
||||||
{
|
|
||||||
m_bInitDone = FALSE;
|
|
||||||
|
|
||||||
m_bUseMinTrack = TRUE;
|
|
||||||
m_bUseMaxTrack = FALSE;
|
|
||||||
m_bUseMaxRect = FALSE;
|
|
||||||
|
|
||||||
m_bShowGrip = TRUE;
|
|
||||||
|
|
||||||
m_bEnableSaveRestore = FALSE;
|
|
||||||
|
|
||||||
m_szGripSize.cx = GetSystemMetrics(SM_CXVSCROLL);
|
|
||||||
m_szGripSize.cy = GetSystemMetrics(SM_CYHSCROLL);
|
|
||||||
}
|
|
||||||
|
|
||||||
CResizableDialog::CResizableDialog()
|
|
||||||
{
|
|
||||||
Construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
CResizableDialog::CResizableDialog(UINT nIDTemplate, CWnd* pParentWnd)
|
|
||||||
: CDialog(nIDTemplate, pParentWnd)
|
|
||||||
{
|
|
||||||
Construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
CResizableDialog::CResizableDialog(LPCTSTR lpszTemplateName, CWnd* pParentWnd)
|
|
||||||
: CDialog(lpszTemplateName, pParentWnd)
|
|
||||||
{
|
|
||||||
Construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
CResizableDialog::~CResizableDialog()
|
|
||||||
{
|
|
||||||
// for safety
|
|
||||||
m_arrLayout.RemoveAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
BEGIN_MESSAGE_MAP(CResizableDialog, CDialog)
|
|
||||||
//{{AFX_MSG_MAP(CResizableDialog)
|
|
||||||
ON_WM_NCHITTEST()
|
|
||||||
ON_WM_GETMINMAXINFO()
|
|
||||||
ON_WM_SIZE()
|
|
||||||
ON_WM_DESTROY()
|
|
||||||
ON_WM_PAINT()
|
|
||||||
//}}AFX_MSG_MAP
|
|
||||||
END_MESSAGE_MAP()
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
// CResizableDialog message handlers
|
|
||||||
|
|
||||||
|
|
||||||
BOOL CResizableDialog::OnInitDialog()
|
|
||||||
{
|
|
||||||
CDialog::OnInitDialog();
|
|
||||||
|
|
||||||
UpdateGripPos();
|
|
||||||
|
|
||||||
// gets the template size as the min track size
|
|
||||||
CRect rc;
|
|
||||||
GetWindowRect(&rc);
|
|
||||||
m_ptMinTrackSize.x = rc.Width();
|
|
||||||
m_ptMinTrackSize.y = rc.Height();
|
|
||||||
|
|
||||||
m_bInitDone = TRUE;
|
|
||||||
|
|
||||||
return TRUE; // return TRUE unless you set the focus to a control
|
|
||||||
// EXCEPTION: OCX Property Pages should return FALSE
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::OnDestroy()
|
|
||||||
{
|
|
||||||
CDialog::OnDestroy();
|
|
||||||
|
|
||||||
if (m_bEnableSaveRestore)
|
|
||||||
SaveWindowRect();
|
|
||||||
|
|
||||||
// remove old windows
|
|
||||||
m_arrLayout.RemoveAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::OnPaint()
|
|
||||||
{
|
|
||||||
CPaintDC dc(this); // device context for painting
|
|
||||||
|
|
||||||
if (m_bShowGrip && !IsZoomed())
|
|
||||||
{
|
|
||||||
// draw size-grip
|
|
||||||
dc.DrawFrameControl(&m_rcGripRect, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::OnSize(UINT nType, int cx, int cy)
|
|
||||||
{
|
|
||||||
CWnd::OnSize(nType, cx, cy);
|
|
||||||
|
|
||||||
if (nType == SIZE_MAXHIDE || nType == SIZE_MAXSHOW)
|
|
||||||
return; // arrangement not needed
|
|
||||||
|
|
||||||
if (m_bInitDone)
|
|
||||||
{
|
|
||||||
ArrangeLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UINT CResizableDialog::OnNcHitTest(CPoint point)
|
|
||||||
{
|
|
||||||
CPoint pt = point;
|
|
||||||
ScreenToClient(&pt);
|
|
||||||
|
|
||||||
// if in size grip and in client area
|
|
||||||
if (m_bShowGrip && m_rcGripRect.PtInRect(pt) &&
|
|
||||||
pt.x >= 0 && pt.y >= 0)
|
|
||||||
return HTBOTTOMRIGHT;
|
|
||||||
|
|
||||||
return CDialog::OnNcHitTest(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
|
|
||||||
{
|
|
||||||
if (!m_bInitDone)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (m_bUseMinTrack)
|
|
||||||
lpMMI->ptMinTrackSize = m_ptMinTrackSize;
|
|
||||||
|
|
||||||
if (m_bUseMaxTrack)
|
|
||||||
lpMMI->ptMaxTrackSize = m_ptMaxTrackSize;
|
|
||||||
|
|
||||||
if (m_bUseMaxRect)
|
|
||||||
{
|
|
||||||
lpMMI->ptMaxPosition = m_ptMaxPos;
|
|
||||||
lpMMI->ptMaxSize = m_ptMaxSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// layout functions
|
|
||||||
|
|
||||||
void CResizableDialog::AddAnchor(HWND wnd, CSize tl_type, CSize br_type)
|
|
||||||
{
|
|
||||||
ASSERT(wnd != NULL && ::IsWindow(wnd));
|
|
||||||
ASSERT(::IsChild(*this, wnd));
|
|
||||||
ASSERT(tl_type != NOANCHOR);
|
|
||||||
|
|
||||||
// get control's window class
|
|
||||||
|
|
||||||
CString st;
|
|
||||||
GetClassName(wnd, st.GetBufferSetLength(MAX_PATH), MAX_PATH);
|
|
||||||
st.ReleaseBuffer();
|
|
||||||
st.MakeUpper();
|
|
||||||
|
|
||||||
// add the style 'clipsiblings' to a GroupBox
|
|
||||||
// to avoid unnecessary repainting of controls inside
|
|
||||||
if (st == "BUTTON")
|
|
||||||
{
|
|
||||||
DWORD style = GetWindowLong(wnd, GWL_STYLE);
|
|
||||||
if (style & BS_GROUPBOX)
|
|
||||||
SetWindowLong(wnd, GWL_STYLE, style | WS_CLIPSIBLINGS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// wnd classes that don't redraw client area correctly
|
|
||||||
// when the hor scroll pos changes due to a resizing
|
|
||||||
BOOL hscroll = FALSE;
|
|
||||||
if (st == "LISTBOX")
|
|
||||||
hscroll = TRUE;
|
|
||||||
|
|
||||||
// wnd classes that need refresh when resized
|
|
||||||
BOOL refresh = FALSE;
|
|
||||||
if (st == "STATIC")
|
|
||||||
{
|
|
||||||
DWORD style = GetWindowLong(wnd, GWL_STYLE);
|
|
||||||
|
|
||||||
switch (style & SS_TYPEMASK)
|
|
||||||
{
|
|
||||||
case SS_LEFT:
|
|
||||||
case SS_CENTER:
|
|
||||||
case SS_RIGHT:
|
|
||||||
// word-wrapped text needs refresh
|
|
||||||
refresh = TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// centered images or text need refresh
|
|
||||||
if (style & SS_CENTERIMAGE)
|
|
||||||
refresh = TRUE;
|
|
||||||
|
|
||||||
// simple text never needs refresh
|
|
||||||
if (style & SS_TYPEMASK == SS_SIMPLE)
|
|
||||||
refresh = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get dialog's and control's rect
|
|
||||||
CRect wndrc, objrc;
|
|
||||||
|
|
||||||
GetClientRect(&wndrc);
|
|
||||||
::GetWindowRect(wnd, &objrc);
|
|
||||||
ScreenToClient(&objrc);
|
|
||||||
|
|
||||||
CSize tl_margin, br_margin;
|
|
||||||
|
|
||||||
if (br_type == NOANCHOR)
|
|
||||||
br_type = tl_type;
|
|
||||||
|
|
||||||
// calculate margin for the top-left corner
|
|
||||||
|
|
||||||
tl_margin.cx = objrc.left - wndrc.Width() * tl_type.cx / 100;
|
|
||||||
tl_margin.cy = objrc.top - wndrc.Height() * tl_type.cy / 100;
|
|
||||||
|
|
||||||
// calculate margin for the bottom-right corner
|
|
||||||
|
|
||||||
br_margin.cx = objrc.right - wndrc.Width() * br_type.cx / 100;
|
|
||||||
br_margin.cy = objrc.bottom - wndrc.Height() * br_type.cy / 100;
|
|
||||||
|
|
||||||
// add to the list
|
|
||||||
Layout obj(wnd, tl_type, tl_margin, br_type, br_margin, hscroll, refresh);
|
|
||||||
m_arrLayout.Add(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::ArrangeLayout()
|
|
||||||
{
|
|
||||||
// update size-grip
|
|
||||||
InvalidateRect(&m_rcGripRect);
|
|
||||||
UpdateGripPos();
|
|
||||||
InvalidateRect(&m_rcGripRect);
|
|
||||||
|
|
||||||
// init some vars
|
|
||||||
CRect wndrc;
|
|
||||||
GetClientRect(&wndrc);
|
|
||||||
|
|
||||||
int i, count = m_arrLayout.GetSize();
|
|
||||||
HDWP hdwp = BeginDeferWindowPos(count);
|
|
||||||
|
|
||||||
for (i=0; i<count; ++i)
|
|
||||||
{
|
|
||||||
Layout& obj = m_arrLayout[i];
|
|
||||||
|
|
||||||
CRect objrc, newrc;
|
|
||||||
CWnd* wnd = CWnd::FromHandle(obj.hwnd); // temporary solution
|
|
||||||
|
|
||||||
wnd->GetWindowRect(&objrc);
|
|
||||||
ScreenToClient(&objrc);
|
|
||||||
|
|
||||||
// calculate new top-left corner
|
|
||||||
|
|
||||||
newrc.left = obj.tl_margin.cx + wndrc.Width() * obj.tl_type.cx / 100;
|
|
||||||
newrc.top = obj.tl_margin.cy + wndrc.Height() * obj.tl_type.cy / 100;
|
|
||||||
|
|
||||||
// calculate new bottom-right corner
|
|
||||||
|
|
||||||
newrc.right = obj.br_margin.cx + wndrc.Width() * obj.br_type.cx / 100;
|
|
||||||
newrc.bottom = obj.br_margin.cy + wndrc.Height() * obj.br_type.cy / 100;
|
|
||||||
|
|
||||||
if (!newrc.EqualRect(&objrc))
|
|
||||||
{
|
|
||||||
if (obj.adj_hscroll)
|
|
||||||
{
|
|
||||||
// needs repainting, due to horiz scrolling
|
|
||||||
int diff = newrc.Width() - objrc.Width();
|
|
||||||
int max = wnd->GetScrollLimit(SB_HORZ);
|
|
||||||
|
|
||||||
obj.need_refresh = FALSE;
|
|
||||||
if (max > 0 && wnd->GetScrollPos(SB_HORZ) > max - diff)
|
|
||||||
{
|
|
||||||
obj.need_refresh = TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set flags
|
|
||||||
DWORD flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION;
|
|
||||||
if (newrc.TopLeft() == objrc.TopLeft())
|
|
||||||
flags |= SWP_NOMOVE;
|
|
||||||
if (newrc.Size() == objrc.Size())
|
|
||||||
flags |= SWP_NOSIZE;
|
|
||||||
|
|
||||||
DeferWindowPos(hdwp, obj.hwnd, NULL, newrc.left, newrc.top,
|
|
||||||
newrc.Width(), newrc.Height(), flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// go re-arrange child windows
|
|
||||||
EndDeferWindowPos(hdwp);
|
|
||||||
|
|
||||||
// refresh those that need
|
|
||||||
for (i=0; i<count; ++i)
|
|
||||||
{
|
|
||||||
Layout& obj = m_arrLayout[i];
|
|
||||||
CWnd* wnd = CWnd::FromHandle(obj.hwnd); // temporary solution
|
|
||||||
|
|
||||||
if (obj.need_refresh)
|
|
||||||
{
|
|
||||||
wnd->Invalidate();
|
|
||||||
wnd->UpdateWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::UpdateGripPos()
|
|
||||||
{
|
|
||||||
// size-grip goes bottom right in the client area
|
|
||||||
|
|
||||||
GetClientRect(&m_rcGripRect);
|
|
||||||
|
|
||||||
m_rcGripRect.left = m_rcGripRect.right - m_szGripSize.cx;
|
|
||||||
m_rcGripRect.top = m_rcGripRect.bottom - m_szGripSize.cy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// protected members
|
|
||||||
|
|
||||||
void CResizableDialog::ShowSizeGrip(BOOL bShow)
|
|
||||||
{
|
|
||||||
if (m_bShowGrip != bShow)
|
|
||||||
{
|
|
||||||
m_bShowGrip = bShow;
|
|
||||||
InvalidateRect(&m_rcGripRect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::SetMaximizedRect(const CRect& rc)
|
|
||||||
{
|
|
||||||
m_bUseMaxRect = TRUE;
|
|
||||||
|
|
||||||
m_ptMaxPos = rc.TopLeft();
|
|
||||||
m_ptMaxSize.x = rc.Width();
|
|
||||||
m_ptMaxSize.y = rc.Height();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::ResetMaximizedRect()
|
|
||||||
{
|
|
||||||
m_bUseMaxRect = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::SetMinTrackSize(const CSize& size)
|
|
||||||
{
|
|
||||||
m_bUseMinTrack = TRUE;
|
|
||||||
|
|
||||||
m_ptMinTrackSize.x = size.cx;
|
|
||||||
m_ptMinTrackSize.y = size.cy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::ResetMinTrackSize()
|
|
||||||
{
|
|
||||||
m_bUseMinTrack = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::SetMaxTrackSize(const CSize& size)
|
|
||||||
{
|
|
||||||
m_bUseMaxTrack = TRUE;
|
|
||||||
|
|
||||||
m_ptMaxTrackSize.x = size.cx;
|
|
||||||
m_ptMaxTrackSize.y = size.cy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::ResetMaxTrackSize()
|
|
||||||
{
|
|
||||||
m_bUseMaxTrack = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this must be called after all the other settings
|
|
||||||
// to have the dialog and its controls displayed properly
|
|
||||||
void CResizableDialog::EnableSaveRestore(LPCTSTR pszSection, LPCTSTR pszEntry)
|
|
||||||
{
|
|
||||||
m_sSection = pszSection;
|
|
||||||
m_sEntry = pszEntry;
|
|
||||||
|
|
||||||
m_bEnableSaveRestore = TRUE;
|
|
||||||
|
|
||||||
LoadWindowRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// used to save/restore window's size and position
|
|
||||||
// either in the registry or a private .INI file
|
|
||||||
// depending on your application settings
|
|
||||||
|
|
||||||
#define PROFILE_FMT _T("%d,%d,%d,%d,%d,%d")
|
|
||||||
|
|
||||||
void CResizableDialog::SaveWindowRect()
|
|
||||||
{
|
|
||||||
CString data;
|
|
||||||
WINDOWPLACEMENT wp;
|
|
||||||
|
|
||||||
ZeroMemory(&wp, sizeof(WINDOWPLACEMENT));
|
|
||||||
wp.length = sizeof(WINDOWPLACEMENT);
|
|
||||||
GetWindowPlacement(&wp);
|
|
||||||
|
|
||||||
RECT& rc = wp.rcNormalPosition; // alias
|
|
||||||
|
|
||||||
data.Format(PROFILE_FMT, rc.left, rc.top,
|
|
||||||
rc.right, rc.bottom, wp.showCmd, wp.flags);
|
|
||||||
|
|
||||||
AfxGetApp()->WriteProfileString(m_sSection, m_sEntry, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CResizableDialog::LoadWindowRect()
|
|
||||||
{
|
|
||||||
CString data;
|
|
||||||
WINDOWPLACEMENT wp;
|
|
||||||
|
|
||||||
data = AfxGetApp()->GetProfileString(m_sSection, m_sEntry);
|
|
||||||
|
|
||||||
if (data.IsEmpty()) // never saved before
|
|
||||||
return;
|
|
||||||
|
|
||||||
ZeroMemory(&wp, sizeof(WINDOWPLACEMENT));
|
|
||||||
wp.length = sizeof(WINDOWPLACEMENT);
|
|
||||||
GetWindowPlacement(&wp);
|
|
||||||
|
|
||||||
RECT& rc = wp.rcNormalPosition; // alias
|
|
||||||
|
|
||||||
if (_stscanf(data, PROFILE_FMT, &rc.left, &rc.top,
|
|
||||||
&rc.right, &rc.bottom, &wp.showCmd, &wp.flags) == 6)
|
|
||||||
{
|
|
||||||
SetWindowPlacement(&wp);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,173 +0,0 @@
|
|||||||
#if !defined(AFX_RESIZABLEDIALOG_H__INCLUDED_)
|
|
||||||
#define AFX_RESIZABLEDIALOG_H__INCLUDED_
|
|
||||||
|
|
||||||
#if _MSC_VER > 1000
|
|
||||||
#pragma once
|
|
||||||
#endif // _MSC_VER > 1000
|
|
||||||
|
|
||||||
// ResizableDialog.h : header file
|
|
||||||
//
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Copyright (C) 2000 by Paolo Messina
|
|
||||||
// (ppescher@yahoo.com)
|
|
||||||
//
|
|
||||||
// Free for non-commercial use.
|
|
||||||
// You may change the code to your needs,
|
|
||||||
// provided that credits to the original
|
|
||||||
// author is given in the modified files.
|
|
||||||
//
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
#include <afxtempl.h>
|
|
||||||
#include <afxwin.h>
|
|
||||||
|
|
||||||
// useful compatibility constants (the only one required is NOANCHOR)
|
|
||||||
|
|
||||||
#if !defined(__SIZE_ANCHORS_)
|
|
||||||
#define __SIZE_ANCHORS_
|
|
||||||
|
|
||||||
const CSize
|
|
||||||
NOANCHOR(-1,-1),
|
|
||||||
TOP_LEFT(0,0), TOP_CENTER(50,0), TOP_RIGHT(100,0),
|
|
||||||
MIDDLE_LEFT(0,50), MIDDLE_CENTER(50,50), MIDDLE_RIGHT(100,50),
|
|
||||||
BOTTOM_LEFT(0,100), BOTTOM_CENTER(50,100), BOTTOM_RIGHT(100,100);
|
|
||||||
|
|
||||||
#endif // !defined(__SIZE_ANCHORS_)
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
// CResizableDialog window
|
|
||||||
|
|
||||||
class CResizableDialog : public CDialog
|
|
||||||
{
|
|
||||||
|
|
||||||
// Construction
|
|
||||||
public:
|
|
||||||
CResizableDialog();
|
|
||||||
CResizableDialog(UINT nIDTemplate, CWnd* pParentWnd = NULL);
|
|
||||||
CResizableDialog(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL);
|
|
||||||
|
|
||||||
// Attributes
|
|
||||||
private:
|
|
||||||
// flags
|
|
||||||
BOOL m_bShowGrip;
|
|
||||||
BOOL m_bUseMaxTrack;
|
|
||||||
BOOL m_bUseMinTrack;
|
|
||||||
BOOL m_bUseMaxRect;
|
|
||||||
BOOL m_bEnableSaveRestore;
|
|
||||||
|
|
||||||
// internal status
|
|
||||||
CString m_sSection; // section name and
|
|
||||||
CString m_sEntry; // entry for save/restore
|
|
||||||
|
|
||||||
BOOL m_bInitDone; // if all internal vars initialized
|
|
||||||
|
|
||||||
SIZE m_szGripSize; // set at construction time
|
|
||||||
|
|
||||||
CRect m_rcGripRect; // current pos of grip
|
|
||||||
|
|
||||||
POINT m_ptMinTrackSize; // min tracking size
|
|
||||||
POINT m_ptMaxTrackSize; // max tracking size
|
|
||||||
POINT m_ptMaxPos; // maximized position
|
|
||||||
POINT m_ptMaxSize; // maximized size
|
|
||||||
|
|
||||||
class Layout
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HWND hwnd;
|
|
||||||
|
|
||||||
BOOL adj_hscroll;
|
|
||||||
BOOL need_refresh;
|
|
||||||
|
|
||||||
// upper-left corner
|
|
||||||
CSize tl_type;
|
|
||||||
CSize tl_margin;
|
|
||||||
|
|
||||||
// bottom-right corner
|
|
||||||
CSize br_type;
|
|
||||||
CSize br_margin;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Layout()
|
|
||||||
: hwnd(NULL), adj_hscroll(FALSE), need_refresh(FALSE),
|
|
||||||
tl_type(0,0), tl_margin(0,0),
|
|
||||||
br_type(0,0), br_margin(0,0)
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
Layout(HWND hw, SIZE tl_t, SIZE tl_m,
|
|
||||||
SIZE br_t, SIZE br_m, BOOL hscroll, BOOL refresh)
|
|
||||||
{
|
|
||||||
hwnd = hw;
|
|
||||||
|
|
||||||
adj_hscroll = hscroll;
|
|
||||||
need_refresh = refresh;
|
|
||||||
|
|
||||||
tl_type = tl_t;
|
|
||||||
tl_margin = tl_m;
|
|
||||||
|
|
||||||
br_type = br_t;
|
|
||||||
br_margin = br_m;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
CArray<Layout, Layout&> m_arrLayout; // list of repositionable controls
|
|
||||||
|
|
||||||
// Operations
|
|
||||||
public:
|
|
||||||
|
|
||||||
// Overrides
|
|
||||||
// ClassWizard generated virtual function overrides
|
|
||||||
//{{AFX_VIRTUAL(CResizableDialog)
|
|
||||||
//}}AFX_VIRTUAL
|
|
||||||
|
|
||||||
// Implementation
|
|
||||||
public:
|
|
||||||
virtual ~CResizableDialog();
|
|
||||||
|
|
||||||
// used internally
|
|
||||||
private:
|
|
||||||
void Construct();
|
|
||||||
void LoadWindowRect();
|
|
||||||
void SaveWindowRect();
|
|
||||||
void ArrangeLayout();
|
|
||||||
void UpdateGripPos();
|
|
||||||
|
|
||||||
// callable from derived classes
|
|
||||||
//protected:
|
|
||||||
public:
|
|
||||||
void AddAnchor(HWND wnd, CSize tl_type,
|
|
||||||
CSize br_type = NOANCHOR); // add anchors to a control
|
|
||||||
void AddAnchor(UINT ctrl_ID, CSize tl_type,
|
|
||||||
CSize br_type = NOANCHOR) // add anchors to a control
|
|
||||||
{
|
|
||||||
AddAnchor(::GetDlgItem(*this, ctrl_ID), tl_type, br_type);
|
|
||||||
};
|
|
||||||
void ShowSizeGrip(BOOL bShow); // show or hide the size grip
|
|
||||||
void SetMaximizedRect(const CRect& rc); // set window rect when maximized
|
|
||||||
void ResetMaximizedRect(); // reset to default maximized rect
|
|
||||||
void SetMinTrackSize(const CSize& size); // set minimum tracking size
|
|
||||||
void ResetMinTrackSize(); // reset to default minimum tracking size
|
|
||||||
void SetMaxTrackSize(const CSize& size); // set maximum tracking size
|
|
||||||
void ResetMaxTrackSize(); // reset to default maximum tracking size
|
|
||||||
void EnableSaveRestore(LPCTSTR pszSection, LPCTSTR pszEntry); // section and entry in app's profile
|
|
||||||
|
|
||||||
// Generated message map functions
|
|
||||||
protected:
|
|
||||||
//{{AFX_MSG(CResizableDialog)
|
|
||||||
virtual BOOL OnInitDialog();
|
|
||||||
afx_msg UINT OnNcHitTest(CPoint point);
|
|
||||||
afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI);
|
|
||||||
afx_msg void OnSize(UINT nType, int cx, int cy);
|
|
||||||
afx_msg void OnDestroy();
|
|
||||||
afx_msg void OnPaint();
|
|
||||||
//}}AFX_MSG
|
|
||||||
DECLARE_MESSAGE_MAP()
|
|
||||||
};
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
//{{AFX_INSERT_LOCATION}}
|
|
||||||
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
|
|
||||||
|
|
||||||
#endif // !defined(AFX_RESIZABLEDIALOG_H__INCLUDED_)
|
|
Loading…
Reference in New Issue
Block a user