Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
vlc-1.1
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
videolan
vlc-1.1
Commits
2f30d77a
Commit
2f30d77a
authored
Jan 31, 2000
by
Vincent Seguin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Changement de cha�ne configurable depuis un fichier texte.
Quelques corrections esthetiques dans vout.
parent
c80107b2
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
330 additions
and
50 deletions
+330
-50
include/common.h
include/common.h
+2
-0
include/config.h
include/config.h
+4
-0
include/interface.h
include/interface.h
+4
-1
src/interface/interface.c
src/interface/interface.c
+302
-24
src/interface/intf_cmd.c
src/interface/intf_cmd.c
+8
-8
src/interface/main.c
src/interface/main.c
+3
-15
src/video_output/video_output.c
src/video_output/video_output.c
+4
-2
vlc.channels
vlc.channels
+3
-0
No files found.
include/common.h
View file @
2f30d77a
...
...
@@ -39,11 +39,13 @@ struct intf_thread_s;
struct
intf_sys_s
;
struct
intf_console_s
;
struct
intf_msg_s
;
struct
intf_channel_s
;
typedef
struct
intf_thread_s
*
p_intf_thread_t
;
typedef
struct
intf_sys_s
*
p_intf_sys_t
;
typedef
struct
intf_console_s
*
p_intf_console_t
;
typedef
struct
intf_msg_s
*
p_intf_msg_t
;
typedef
struct
intf_channel_s
*
p_intf_channel_t
;
/* Input */
struct
input_thread_s
;
...
...
include/config.h
View file @
2f30d77a
...
...
@@ -100,6 +100,10 @@
#define INTF_INIT_SCRIPT_VAR "vlc_init"
#define INTF_INIT_SCRIPT_DEFAULT "vlc.init"
/* Environment variable used to store channels file and default value */
#define INTF_CHANNELS_VAR "vlc_channels"
#define INTF_CHANNELS_DEFAULT "vlc.channels"
/* Base delay in micro second for interface sleeps */
#define INTF_IDLE_SLEEP 100000
...
...
include/interface.h
View file @
2f30d77a
...
...
@@ -34,6 +34,9 @@ typedef struct intf_thread_s
p_intf_console_t
p_console
;
/* console */
p_intf_sys_t
p_sys
;
/* system interface */
/* Channels array - NULL if not used */
p_intf_channel_t
p_channel
;
/* description of channels */
/* Main threads - NULL if not active */
p_vout_thread_t
p_vout
;
p_input_thread_t
p_input
;
...
...
@@ -46,6 +49,6 @@ intf_thread_t * intf_Create ( void );
void
intf_Run
(
intf_thread_t
*
p_intf
);
void
intf_Destroy
(
intf_thread_t
*
p_intf
);
int
intf_Select
Input
(
intf_thread_t
*
p_intf
,
int
i_index
);
int
intf_Select
Channel
(
intf_thread_t
*
p_intf
,
int
i_channel
);
int
intf_ProcessKey
(
intf_thread_t
*
p_intf
,
int
i_key
);
src/interface/interface.c
View file @
2f30d77a
...
...
@@ -31,6 +31,34 @@
#include "intf_sys.h"
/*******************************************************************************
* intf_channel_t: channel description
*******************************************************************************
* A 'channel' is a descriptor of an input method. It is used to switch easily
* from source to source without having to specify the whole input thread
* configuration. The channels array, stored in the interface thread object, is
* loaded in intf_Create, and unloaded in intf_Destroy.
*******************************************************************************/
typedef
struct
intf_channel_s
{
/* Channel description */
int
i_channel
;
/* channel number, -1 for end of array */
char
*
psz_description
;
/* channel description (owned) */
/* Input configuration */
int
i_input_method
;
/* input method descriptor */
char
*
psz_input_source
;
/* source string (owned) */
int
i_input_port
;
/* port */
int
i_input_vlan
;
/* vlan */
}
intf_channel_t
;
/*******************************************************************************
* Local prototypes
*******************************************************************************/
static
int
LoadChannels
(
intf_thread_t
*
p_intf
,
char
*
psz_filename
);
static
void
UnloadChannels
(
intf_thread_t
*
p_intf
);
static
int
ParseChannel
(
intf_channel_t
*
p_channel
,
char
*
psz_str
);
/*******************************************************************************
* intf_Create: prepare interface before main loop
*******************************************************************************
...
...
@@ -54,6 +82,11 @@ intf_thread_t* intf_Create( void )
p_intf
->
p_vout
=
NULL
;
p_intf
->
p_input
=
NULL
;
/* Load channels - the pointer will be set to NULL on failure. The
* return value is ignored since the program can work without
* channels */
LoadChannels
(
p_intf
,
main_GetPszVariable
(
INTF_CHANNELS_VAR
,
INTF_CHANNELS_DEFAULT
));
/* Start interfaces */
p_intf
->
p_console
=
intf_ConsoleCreate
();
if
(
p_intf
->
p_console
==
NULL
)
...
...
@@ -127,26 +160,33 @@ void intf_Destroy( intf_thread_t *p_intf )
intf_SysDestroy
(
p_intf
);
intf_ConsoleDestroy
(
p_intf
->
p_console
);
/* Unload channels */
UnloadChannels
(
p_intf
);
/* Free structure */
free
(
p_intf
);
}
/*******************************************************************************
* intf_Select
Input: change input stream
* intf_Select
Channel: change channel
*******************************************************************************
* Kill existing input, if any, and try to open a new one, using an input
* configuration table.
*******************************************************************************/
int
intf_Select
Input
(
intf_thread_t
*
p_intf
,
int
i_index
)
int
intf_Select
Channel
(
intf_thread_t
*
p_intf
,
int
i_channel
)
{
intf_
DbgMsg
(
"
\n
"
);
intf_
channel_t
*
p_channel
;
/* channel */
/*
If VLANs are not active, return with an error
*/
if
(
!
p_main
->
b_vlans
)
/*
Look for channel in array
*/
if
(
p_intf
->
p_channel
!=
NULL
)
{
intf_ErrMsg
(
"error: VLANs are not activated
\n
"
);
return
(
1
);
}
for
(
p_channel
=
p_intf
->
p_channel
;
p_channel
->
i_channel
!=
-
1
;
p_channel
++
)
{
if
(
p_channel
->
i_channel
==
i_channel
)
{
/*
* Change channel
*/
/* Kill existing input, if any */
if
(
p_intf
->
p_input
!=
NULL
)
...
...
@@ -154,11 +194,20 @@ int intf_SelectInput( intf_thread_t * p_intf, int i_index )
input_DestroyThread
(
p_intf
->
p_input
,
NULL
);
}
intf_Msg
(
"Channel %d: %s
\n
"
,
i_channel
,
p_channel
->
psz_description
);
/* Open a new input */
intf_Msg
(
"Switching to channel %d
\n
"
,
i_index
);
p_intf
->
p_input
=
input_CreateThread
(
INPUT_METHOD_TS_VLAN_BCAST
,
NULL
,
0
,
i_index
,
p_intf
->
p_input
=
input_CreateThread
(
p_channel
->
i_input_method
,
p_channel
->
psz_input_source
,
p_channel
->
i_input_port
,
p_channel
->
i_input_vlan
,
p_intf
->
p_vout
,
p_main
->
p_aout
,
NULL
);
return
(
p_intf
->
p_input
==
NULL
);
}
}
}
/* Channel does not exist */
intf_Msg
(
"Channel %d does not exist
\n
"
,
i_channel
);
return
(
1
);
}
/*******************************************************************************
...
...
@@ -186,10 +235,9 @@ int intf_ProcessKey( intf_thread_t *p_intf, int i_key )
case
'7'
:
case
'8'
:
case
'9'
:
if
(
intf_SelectInput
(
p_intf
,
i_key
-
'0'
)
)
{
intf_ErrMsg
(
"error: can not open channel %d
\n
"
,
i_key
-
'0'
);
}
/* Change channel - return code is ignored since SelectChannel displays
* its own error messages */
intf_SelectChannel
(
p_intf
,
i_key
-
'0'
);
break
;
case
'+'
:
/* volume + */
// ??
...
...
@@ -262,5 +310,235 @@ int intf_ProcessKey( intf_thread_t *p_intf, int i_key )
return
(
0
);
}
/* following functions are local */
/*******************************************************************************
* LoadChannels: load channels description from a file
*******************************************************************************
* This structe describes all interface-specific data of the main (interface)
* thread.
* Each line of the file is a semicolon separated list of the following
* fields :
* integer channel number
* string channel description
* integer input method (see input.h)
* string input source
* integer input port
* integer input vlan
* The last field must end with a semicolon.
* Comments and empty lines are not explicitely allowed, but lines with parsing
* errors are ignored without warning.
*******************************************************************************/
static
int
LoadChannels
(
intf_thread_t
*
p_intf
,
char
*
psz_filename
)
{
FILE
*
p_file
;
/* file */
intf_channel_t
*
p_channel
;
/* current channel */
char
psz_line
[
INTF_MAX_CMD_SIZE
];
/* line buffer */
int
i_index
;
/* channel or field index */
/* Set default value */
p_intf
->
p_channel
=
NULL
;
/* Open file */
p_file
=
fopen
(
psz_filename
,
"r"
);
if
(
p_file
==
NULL
)
{
intf_ErrMsg
(
"error: can't open %s (%s)
\n
"
,
psz_filename
,
strerror
(
errno
));
return
(
1
);
}
/* First pass: count number of lines */
for
(
i_index
=
0
;
fgets
(
psz_line
,
INTF_MAX_CMD_SIZE
,
p_file
)
!=
NULL
;
i_index
++
)
{
;
}
if
(
i_index
!=
0
)
{
/* Allocate array and rewind - some of the lines may be invalid, and the
* array will probably be larger than the actual number of channels, but
* it has no consequence. */
p_intf
->
p_channel
=
malloc
(
sizeof
(
intf_channel_t
)
*
i_index
);
if
(
p_intf
->
p_channel
==
NULL
)
{
intf_ErrMsg
(
"error: %s
\n
"
,
strerror
(
ENOMEM
));
fclose
(
p_file
);
return
(
1
);
}
p_channel
=
p_intf
->
p_channel
;
rewind
(
p_file
);
/* Second pass: read channels descriptions */
while
(
fgets
(
psz_line
,
INTF_MAX_CMD_SIZE
,
p_file
)
!=
NULL
)
{
if
(
!
ParseChannel
(
p_channel
,
psz_line
)
)
{
intf_DbgMsg
(
"channel [%d] %s : method %d (%s:%d vlan %d)
\n
"
,
p_channel
->
i_channel
,
p_channel
->
psz_description
,
p_channel
->
i_input_method
,
p_channel
->
psz_input_source
,
p_channel
->
i_input_port
,
p_channel
->
i_input_vlan
);
p_channel
++
;
}
}
/* Add marker at the end of the array */
p_channel
->
i_channel
=
-
1
;
}
/* Close file */
fclose
(
p_file
);
return
(
0
);
}
/******************************************************************************
* UnloadChannels: unload channels description
******************************************************************************
* This function free all resources allocated by LoadChannels, if any.
******************************************************************************/
static
void
UnloadChannels
(
intf_thread_t
*
p_intf
)
{
int
i_channel
;
/* channel index */
if
(
p_intf
->
p_channel
!=
NULL
)
{
/* Free allocated strings */
for
(
i_channel
=
0
;
p_intf
->
p_channel
[
i_channel
].
i_channel
!=
-
1
;
i_channel
++
)
{
if
(
p_intf
->
p_channel
[
i_channel
].
psz_description
!=
NULL
)
{
free
(
p_intf
->
p_channel
[
i_channel
].
psz_description
);
}
if
(
p_intf
->
p_channel
[
i_channel
].
psz_input_source
!=
NULL
)
{
free
(
p_intf
->
p_channel
[
i_channel
].
psz_input_source
);
}
}
/* Free array */
free
(
p_intf
->
p_channel
);
p_intf
->
p_channel
=
NULL
;
}
}
/*******************************************************************************
* ParseChannel: parse a channel description line
*******************************************************************************
* See LoadChannels. This function return non 0 on parsing error.
*******************************************************************************/
static
int
ParseChannel
(
intf_channel_t
*
p_channel
,
char
*
psz_str
)
{
char
*
psz_index
;
/* current character */
char
*
psz_end
;
/* end pointer for strtol */
int
i_field
;
/* field number, -1 on error */
int
i_field_length
;
/* field length, for text fields */
/* Set some default fields */
p_channel
->
i_channel
=
0
;
p_channel
->
psz_description
=
NULL
;
p_channel
->
i_input_method
=
0
;
p_channel
->
psz_input_source
=
NULL
;
p_channel
->
i_input_port
=
0
;
p_channel
->
i_input_vlan
=
0
;
/* Parse string */
i_field
=
0
;
for
(
psz_index
=
psz_str
;
(
i_field
!=
-
1
)
&&
(
*
psz_index
!=
'\0'
);
psz_index
++
)
{
if
(
*
psz_index
==
';'
)
{
/* Mark end of field */
*
psz_index
=
'\0'
;
/* Parse field */
switch
(
i_field
++
)
{
case
0
:
/* channel number */
p_channel
->
i_channel
=
strtol
(
psz_str
,
&
psz_end
,
0
);
if
(
(
*
psz_str
==
'\0'
)
||
(
*
psz_end
!=
'\0'
)
)
{
i_field
=
-
1
;
}
break
;
case
1
:
/* channel description */
i_field_length
=
strlen
(
psz_str
);
if
(
i_field_length
!=
0
)
{
p_channel
->
psz_description
=
malloc
(
i_field_length
+
1
);
if
(
p_channel
->
psz_description
==
NULL
)
{
intf_ErrMsg
(
"error: %s
\n
"
,
strerror
(
ENOMEM
));
i_field
=
-
1
;
}
else
{
strcpy
(
p_channel
->
psz_description
,
psz_str
);
}
}
break
;
case
2
:
/* input method */
p_channel
->
i_input_method
=
strtol
(
psz_str
,
&
psz_end
,
0
);
if
(
(
*
psz_str
==
'\0'
)
||
(
*
psz_end
!=
'\0'
)
)
{
i_field
=
-
1
;
}
break
;
case
3
:
/* input source */
i_field_length
=
strlen
(
psz_str
);
if
(
i_field_length
!=
0
)
{
p_channel
->
psz_input_source
=
malloc
(
i_field_length
+
1
);
if
(
p_channel
->
psz_input_source
==
NULL
)
{
intf_ErrMsg
(
"error: %s
\n
"
,
strerror
(
ENOMEM
));
i_field
=
-
1
;
}
else
{
strcpy
(
p_channel
->
psz_input_source
,
psz_str
);
}
}
break
;
case
4
:
/* input port */
p_channel
->
i_input_port
=
strtol
(
psz_str
,
&
psz_end
,
0
);
if
(
(
*
psz_str
==
'\0'
)
||
(
*
psz_end
!=
'\0'
)
)
{
i_field
=
-
1
;
}
break
;
case
5
:
/* input vlan */
p_channel
->
i_channel
=
strtol
(
psz_str
,
&
psz_end
,
0
);
if
(
(
*
psz_str
==
'\0'
)
||
(
*
psz_end
!=
'\0'
)
)
{
i_field
=
-
1
;
}
break
;
/* ... following fields are ignored */
}
/* Set new beginning of field */
psz_str
=
psz_index
+
1
;
}
}
/* At least the first three fields must be parsed sucessfully for function
* success. Other parsing errors are returned using i_field = -1. */
if
(
i_field
<
3
)
{
/* Function fails. Free allocated strings */
if
(
p_channel
->
psz_description
!=
NULL
)
{
free
(
p_channel
->
psz_description
);
}
if
(
p_channel
->
psz_input_source
!=
NULL
)
{
free
(
p_channel
->
psz_input_source
);
}
return
(
1
);
}
/* Return success */
return
(
0
);
}
src/interface/intf_cmd.c
View file @
2f30d77a
...
...
@@ -69,7 +69,7 @@ int intf_ExecCommand( char *psz_cmd )
int
i_index
;
/* multi-purposes index */
int
i_return
;
/* command return value */
intf_DbgMsg
(
"
intf debug:
command `%s'
\n
"
,
psz_cmd
);
intf_DbgMsg
(
"command `%s'
\n
"
,
psz_cmd
);
/* Parse command line (separate arguments). If nothing has been found,
* the function returns without error */
...
...
@@ -110,7 +110,7 @@ int intf_ExecCommand( char *psz_cmd )
{
case
INTF_FATAL_ERROR
:
/* fatal error */
/* Print message and terminates the interface thread */
intf_ErrMsg
(
"
intf fatal:
in command `%s'
\n
"
,
psz_argv
[
0
]
);
intf_ErrMsg
(
"
fatal error
in command `%s'
\n
"
,
psz_argv
[
0
]
);
p_main
->
p_intf
->
b_die
=
1
;
break
;
...
...
@@ -118,7 +118,7 @@ int intf_ExecCommand( char *psz_cmd )
/* Print message, flush messages queue and exit. Note that this
* error should be very rare since it does not even try to cancel other
* threads... */
intf_ErrMsg
(
"
intf critical:
in command `%s'. Please report this error !
\n
"
,
psz_argv
[
0
]
);
intf_ErrMsg
(
"
critical error
in command `%s'. Please report this error !
\n
"
,
psz_argv
[
0
]
);
intf_FlushMsg
();
exit
(
INTF_CRITICAL_ERROR
);
break
;
...
...
@@ -153,7 +153,7 @@ int intf_ExecScript( char *psz_filename )
p_file
=
fopen
(
psz_filename
,
"r"
);
if
(
p_file
==
NULL
)
{
intf_ErrMsg
(
"
intf error
: %s: %s
\n
"
,
psz_filename
,
strerror
(
errno
));
intf_ErrMsg
(
"
warning
: %s: %s
\n
"
,
psz_filename
,
strerror
(
errno
));
return
(
-
1
);
}
...
...
@@ -180,7 +180,7 @@ int intf_ExecScript( char *psz_filename )
}
if
(
!
feof
(
p_file
)
)
{
intf_ErrMsg
(
"
intf
error: %s: %s
\n
"
,
psz_filename
,
strerror
(
errno
));
intf_ErrMsg
(
"error: %s: %s
\n
"
,
psz_filename
,
strerror
(
errno
));
return
(
-
1
);
}
...
...
@@ -393,7 +393,7 @@ static int CheckCommandArguments( intf_arg_t argv[INTF_MAX_ARGS], int i_argc,
return
(
1
);
}
intf_DbgMsg
(
"
intf debug:
argument flags=0x%x (index=%d) name=%s str=%s int=%d float=%f
\n
"
,
intf_DbgMsg
(
"argument flags=0x%x (index=%d) name=%s str=%s int=%d float=%f
\n
"
,
argv
[
i_arg
].
i_flags
,
argv
[
i_arg
].
i_index
,
(
argv
[
i_arg
].
i_flags
&
INTF_NAMED_ARG
)
?
argv
[
i_arg
].
ps_name
:
"NA"
,
...
...
@@ -462,7 +462,7 @@ static int ConvertArgument( intf_arg_t *p_arg, int i_flags, char *psz_str )
#ifdef DEBUG
else
/* error: missing type specifier */
{
intf_ErrMsg
(
"
intf
error: missing type specifier for `%s' (0x%x)
\n
"
,
psz_str
,
i_flags
);
intf_ErrMsg
(
"error: missing type specifier for `%s' (0x%x)
\n
"
,
psz_str
,
i_flags
);
return
(
1
);
}
#endif
...
...
@@ -531,7 +531,7 @@ static void ParseFormatString( intf_arg_t format[INTF_MAX_ARGS], char *psz_forma
break
;
#ifdef DEBUG
default:
/* error which should never happen: incorrect format */
intf_DbgMsg
(
"
intf
error: incorrect format string `%s'
\n
"
,
psz_format
);
intf_DbgMsg
(
"error: incorrect format string `%s'
\n
"
,
psz_format
);
break
;
#endif
}
...
...
src/interface/main.c
View file @
2f30d77a
...
...
@@ -399,11 +399,9 @@ static void Usage( void )
/* Options */
intf_Msg
(
"Options:
\n
"
\
" -h, --help
\t
print usage
\n
"
\
" -v, --version
\t
print program version
\n
"
\
" --noaudio
\t
disable audio
\n
"
\
" -h, --help, -v, --version
\t
print usage or version
\n
"
\
" --noaudio, --novideo
\t
disable audio/video
\n
"
\
" --stereo, --mono
\t
stereo/mono audio
\n
"
\
" --novideo
\t
disable video
\n
"
\
" --display <display>
\t
display string
\n
"
\
" --width <w>, --height <h>
\t
display dimensions
\n
"
\
" -g, --grayscale, --color
\t
grayscale/color video
\n
"
\
...
...
@@ -414,6 +412,7 @@ static void Usage( void )
/* Interface parameters */
intf_Msg
(
"Interface parameters:
\n
"
\
" "
INTF_INIT_SCRIPT_VAR
"=<filename>
\t
initialization script
\n
"
\
" "
INTF_CHANNELS_VAR
"=<filename>
\t
channels list
\n
"
\
);
/* Audio parameters */
...
...
@@ -440,17 +439,6 @@ static void Usage( void )
" "
INPUT_VLAN_SERVER_VAR
"=<hostname>
\t
vlan server
\n
"
\
" "
INPUT_VLAN_PORT_VAR
"=<port>
\t
vlan server port
\n
"
\
);
/* Interfaces keys */
intf_Msg
(
"Interface keys: most interfaces accept the following commands:
\n
"
\
" [space]
\t
toggle interface
\n
"
" [esc], q
\t
quit
\n
"
\
" 0 - 9
\t
select channel
\n
"
\
" +, -, m
\t
change volume, mute
\n
"
\
" g, G, c
\t
change gamma, toggle grayscale
\n
"
\
" i
\t
toggle info printing
\n
"
\
" s
\t
toggle picture scaling
\n
"
\
);
}
/*******************************************************************************
...
...
src/video_output/video_output.c
View file @
2f30d77a
...
...
@@ -808,7 +808,6 @@ static void RunThread( vout_thread_t *p_vout)
}
else
if
(
p_vout
->
b_active
)
/* idle or interface screen alone */
{
//?? clear: SetBufferPicture( p_vout, NULL );
if
(
p_vout
->
b_interface
&&
0
/* && ?? intf_change */
)
{
/* Interface has changed, so a new rendering is required - force
...
...
@@ -1403,6 +1402,8 @@ static void RenderPictureInfo( vout_thread_t *p_vout, picture_t *p_pic )
* nothing has been rendered, or 1 if something has been changed on the screen.
* Note that if you absolutely want something to be printed, you will have
* to force it by setting the last idle date to 0.
* Unlike other rendering functions, this one calls the SetBufferPicture
* function when needed.
******************************************************************************/
static
int
RenderIdle
(
vout_thread_t
*
p_vout
)
{
...
...
@@ -1416,6 +1417,7 @@ static int RenderIdle( vout_thread_t *p_vout )
if
(
(
current_date
-
p_vout
->
last_display_date
)
>
VOUT_IDLE_DELAY
&&
(
current_date
-
p_vout
->
last_idle_date
)
>
VOUT_IDLE_DELAY
)
{
SetBufferPicture
(
p_vout
,
NULL
);
vout_TextSize
(
p_vout
->
p_large_font
,
WIDE_TEXT
|
OUTLINED_TEXT
,
psz_text
,
&
i_width
,
&
i_height
);
if
(
!
Align
(
p_vout
,
&
i_x
,
&
i_y
,
i_width
,
i_height
,
CENTER_RALIGN
,
CENTER_RALIGN
)
)
...
...
vlc.channels
0 → 100644
View file @
2f30d77a
0;Ptyx (caribou);20;caribou.via.ecp.fr;
1;Sam (bofh);20;bofh.via.ecp.fr;
2;Polux (dressler);20;dressler.via.ecp.fr;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment