00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055 #include "asterisk.h"
00056
00057 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 366408 $")
00058
00059 #include <sys/signal.h>
00060
00061 #include <portaudio.h>
00062
00063 #include "asterisk/module.h"
00064 #include "asterisk/channel.h"
00065 #include "asterisk/pbx.h"
00066 #include "asterisk/causes.h"
00067 #include "asterisk/cli.h"
00068 #include "asterisk/musiconhold.h"
00069 #include "asterisk/callerid.h"
00070 #include "asterisk/astobj2.h"
00071
00072
00073
00074
00075
00076
00077
00078 #define SAMPLE_RATE 16000
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090 #define NUM_SAMPLES 320
00091
00092
00093 #define INPUT_CHANNELS 1
00094
00095
00096 #define OUTPUT_CHANNELS 1
00097
00098
00099
00100
00101
00102
00103 #define TEXT_SIZE 256
00104
00105
00106 #define V_BEGIN " --- <(\"<) --- "
00107 #define V_END " --- (>\")> ---\n"
00108
00109
00110 static const char config_file[] = "console.conf";
00111
00112
00113
00114
00115
00116
00117
00118 static struct console_pvt {
00119 AST_DECLARE_STRING_FIELDS(
00120
00121 AST_STRING_FIELD(name);
00122 AST_STRING_FIELD(input_device);
00123 AST_STRING_FIELD(output_device);
00124
00125 AST_STRING_FIELD(context);
00126
00127 AST_STRING_FIELD(exten);
00128
00129 AST_STRING_FIELD(cid_num);
00130
00131 AST_STRING_FIELD(cid_name);
00132
00133
00134
00135 AST_STRING_FIELD(mohinterpret);
00136
00137 AST_STRING_FIELD(language);
00138
00139 AST_STRING_FIELD(parkinglot);
00140 );
00141
00142 struct ast_channel *owner;
00143
00144 PaStream *stream;
00145
00146 struct ast_frame fr;
00147
00148 unsigned int streamstate:1;
00149
00150 unsigned int hookstate:1;
00151
00152 unsigned int muted:1;
00153
00154 unsigned int autoanswer:1;
00155
00156 unsigned int overridecontext:1;
00157
00158
00159 unsigned int destroy:1;
00160
00161 pthread_t thread;
00162 } globals;
00163
00164 AST_MUTEX_DEFINE_STATIC(globals_lock);
00165
00166 static struct ao2_container *pvts;
00167 #define NUM_PVT_BUCKETS 7
00168
00169 static struct console_pvt *active_pvt;
00170 AST_RWLOCK_DEFINE_STATIC(active_lock);
00171
00172
00173
00174
00175
00176
00177
00178 static struct ast_jb_conf default_jbconf = {
00179 .flags = 0,
00180 .max_size = 200,
00181 .resync_threshold = 1000,
00182 .impl = "fixed",
00183 .target_extra = 40,
00184 };
00185 static struct ast_jb_conf global_jbconf;
00186
00187
00188 static struct ast_channel *console_request(const char *type, struct ast_format_cap *cap,
00189 const struct ast_channel *requestor, const char *data, int *cause);
00190 static int console_digit_begin(struct ast_channel *c, char digit);
00191 static int console_digit_end(struct ast_channel *c, char digit, unsigned int duration);
00192 static int console_text(struct ast_channel *c, const char *text);
00193 static int console_hangup(struct ast_channel *c);
00194 static int console_answer(struct ast_channel *c);
00195 static struct ast_frame *console_read(struct ast_channel *chan);
00196 static int console_call(struct ast_channel *c, const char *dest, int timeout);
00197 static int console_write(struct ast_channel *chan, struct ast_frame *f);
00198 static int console_indicate(struct ast_channel *chan, int cond,
00199 const void *data, size_t datalen);
00200 static int console_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
00201
00202
00203 static struct ast_channel_tech console_tech = {
00204 .type = "Console",
00205 .description = "Console Channel Driver",
00206 .requester = console_request,
00207 .send_digit_begin = console_digit_begin,
00208 .send_digit_end = console_digit_end,
00209 .send_text = console_text,
00210 .hangup = console_hangup,
00211 .answer = console_answer,
00212 .read = console_read,
00213 .call = console_call,
00214 .write = console_write,
00215 .indicate = console_indicate,
00216 .fixup = console_fixup,
00217 };
00218
00219
00220 #define console_pvt_lock(pvt) ao2_lock(pvt)
00221
00222
00223 #define console_pvt_unlock(pvt) ao2_unlock(pvt)
00224
00225 static inline struct console_pvt *ref_pvt(struct console_pvt *pvt)
00226 {
00227 if (pvt)
00228 ao2_ref(pvt, +1);
00229 return pvt;
00230 }
00231
00232 static inline struct console_pvt *unref_pvt(struct console_pvt *pvt)
00233 {
00234 ao2_ref(pvt, -1);
00235 return NULL;
00236 }
00237
00238 static struct console_pvt *find_pvt(const char *name)
00239 {
00240 struct console_pvt tmp_pvt = {
00241 .name = name,
00242 };
00243
00244 return ao2_find(pvts, &tmp_pvt, OBJ_POINTER);
00245 }
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257 static void *stream_monitor(void *data)
00258 {
00259 struct console_pvt *pvt = data;
00260 char buf[NUM_SAMPLES * sizeof(int16_t)];
00261 PaError res;
00262 struct ast_frame f = {
00263 .frametype = AST_FRAME_VOICE,
00264 .src = "console_stream_monitor",
00265 .data.ptr = buf,
00266 .datalen = sizeof(buf),
00267 .samples = sizeof(buf) / sizeof(int16_t),
00268 };
00269 ast_format_set(&f.subclass.format, AST_FORMAT_SLINEAR16, 0);
00270
00271 for (;;) {
00272 pthread_testcancel();
00273 res = Pa_ReadStream(pvt->stream, buf, sizeof(buf) / sizeof(int16_t));
00274 pthread_testcancel();
00275
00276 if (!pvt->owner) {
00277 return NULL;
00278 }
00279
00280 if (res == paNoError)
00281 ast_queue_frame(pvt->owner, &f);
00282 }
00283
00284 return NULL;
00285 }
00286
00287 static int open_stream(struct console_pvt *pvt)
00288 {
00289 int res = paInternalError;
00290
00291 if (!strcasecmp(pvt->input_device, "default") &&
00292 !strcasecmp(pvt->output_device, "default")) {
00293 res = Pa_OpenDefaultStream(&pvt->stream, INPUT_CHANNELS, OUTPUT_CHANNELS,
00294 paInt16, SAMPLE_RATE, NUM_SAMPLES, NULL, NULL);
00295 } else {
00296 PaStreamParameters input_params = {
00297 .channelCount = 1,
00298 .sampleFormat = paInt16,
00299 .suggestedLatency = (1.0 / 50.0),
00300 .device = paNoDevice,
00301 };
00302 PaStreamParameters output_params = {
00303 .channelCount = 1,
00304 .sampleFormat = paInt16,
00305 .suggestedLatency = (1.0 / 50.0),
00306 .device = paNoDevice,
00307 };
00308 PaDeviceIndex idx, num_devices, def_input, def_output;
00309
00310 if (!(num_devices = Pa_GetDeviceCount()))
00311 return res;
00312
00313 def_input = Pa_GetDefaultInputDevice();
00314 def_output = Pa_GetDefaultOutputDevice();
00315
00316 for (idx = 0;
00317 idx < num_devices && (input_params.device == paNoDevice
00318 || output_params.device == paNoDevice);
00319 idx++)
00320 {
00321 const PaDeviceInfo *dev = Pa_GetDeviceInfo(idx);
00322
00323 if (dev->maxInputChannels) {
00324 if ( (idx == def_input && !strcasecmp(pvt->input_device, "default")) ||
00325 !strcasecmp(pvt->input_device, dev->name) )
00326 input_params.device = idx;
00327 }
00328
00329 if (dev->maxOutputChannels) {
00330 if ( (idx == def_output && !strcasecmp(pvt->output_device, "default")) ||
00331 !strcasecmp(pvt->output_device, dev->name) )
00332 output_params.device = idx;
00333 }
00334 }
00335
00336 if (input_params.device == paNoDevice)
00337 ast_log(LOG_ERROR, "No input device found for console device '%s'\n", pvt->name);
00338 if (output_params.device == paNoDevice)
00339 ast_log(LOG_ERROR, "No output device found for console device '%s'\n", pvt->name);
00340
00341 res = Pa_OpenStream(&pvt->stream, &input_params, &output_params,
00342 SAMPLE_RATE, NUM_SAMPLES, paNoFlag, NULL, NULL);
00343 }
00344
00345 return res;
00346 }
00347
00348 static int start_stream(struct console_pvt *pvt)
00349 {
00350 PaError res;
00351 int ret_val = 0;
00352
00353 console_pvt_lock(pvt);
00354
00355
00356
00357
00358 if (pvt->streamstate || !pvt->owner)
00359 goto return_unlock;
00360
00361 pvt->streamstate = 1;
00362 ast_debug(1, "Starting stream\n");
00363
00364 res = open_stream(pvt);
00365 if (res != paNoError) {
00366 ast_log(LOG_WARNING, "Failed to open stream - (%d) %s\n",
00367 res, Pa_GetErrorText(res));
00368 ret_val = -1;
00369 goto return_unlock;
00370 }
00371
00372 res = Pa_StartStream(pvt->stream);
00373 if (res != paNoError) {
00374 ast_log(LOG_WARNING, "Failed to start stream - (%d) %s\n",
00375 res, Pa_GetErrorText(res));
00376 ret_val = -1;
00377 goto return_unlock;
00378 }
00379
00380 if (ast_pthread_create_background(&pvt->thread, NULL, stream_monitor, pvt)) {
00381 ast_log(LOG_ERROR, "Failed to start stream monitor thread\n");
00382 ret_val = -1;
00383 }
00384
00385 return_unlock:
00386 console_pvt_unlock(pvt);
00387
00388 return ret_val;
00389 }
00390
00391 static int stop_stream(struct console_pvt *pvt)
00392 {
00393 if (!pvt->streamstate || pvt->thread == AST_PTHREADT_NULL)
00394 return 0;
00395
00396 pthread_cancel(pvt->thread);
00397 pthread_kill(pvt->thread, SIGURG);
00398 pthread_join(pvt->thread, NULL);
00399
00400 console_pvt_lock(pvt);
00401 Pa_AbortStream(pvt->stream);
00402 Pa_CloseStream(pvt->stream);
00403 pvt->stream = NULL;
00404 pvt->streamstate = 0;
00405 console_pvt_unlock(pvt);
00406
00407 return 0;
00408 }
00409
00410
00411
00412
00413 static struct ast_channel *console_new(struct console_pvt *pvt, const char *ext, const char *ctx, int state, const char *linkedid)
00414 {
00415 struct ast_channel *chan;
00416
00417 if (!(chan = ast_channel_alloc(1, state, pvt->cid_num, pvt->cid_name, NULL,
00418 ext, ctx, linkedid, 0, "Console/%s", pvt->name))) {
00419 return NULL;
00420 }
00421
00422 ast_channel_tech_set(chan, &console_tech);
00423 ast_format_set(ast_channel_readformat(chan), AST_FORMAT_SLINEAR16, 0);
00424 ast_format_set(ast_channel_writeformat(chan), AST_FORMAT_SLINEAR16, 0);
00425 ast_format_cap_add(ast_channel_nativeformats(chan), ast_channel_readformat(chan));
00426 ast_channel_tech_pvt_set(chan, ref_pvt(pvt));
00427
00428 pvt->owner = chan;
00429
00430 if (!ast_strlen_zero(pvt->language))
00431 ast_channel_language_set(chan, pvt->language);
00432
00433 ast_jb_configure(chan, &global_jbconf);
00434
00435 if (state != AST_STATE_DOWN) {
00436 if (ast_pbx_start(chan)) {
00437 ast_channel_hangupcause_set(chan, AST_CAUSE_SWITCH_CONGESTION);
00438 ast_hangup(chan);
00439 chan = NULL;
00440 } else
00441 start_stream(pvt);
00442 }
00443
00444 return chan;
00445 }
00446
00447 static struct ast_channel *console_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
00448 {
00449 struct ast_channel *chan = NULL;
00450 struct console_pvt *pvt;
00451 char buf[512];
00452
00453 if (!(pvt = find_pvt(data))) {
00454 ast_log(LOG_ERROR, "Console device '%s' not found\n", data);
00455 return NULL;
00456 }
00457
00458 if (!(ast_format_cap_has_joint(cap, console_tech.capabilities))) {
00459 ast_log(LOG_NOTICE, "Channel requested with unsupported format(s): '%s'\n", ast_getformatname_multiple(buf, sizeof(buf), cap));
00460 goto return_unref;
00461 }
00462
00463 if (pvt->owner) {
00464 ast_log(LOG_NOTICE, "Console channel already active!\n");
00465 *cause = AST_CAUSE_BUSY;
00466 goto return_unref;
00467 }
00468
00469 console_pvt_lock(pvt);
00470 chan = console_new(pvt, NULL, NULL, AST_STATE_DOWN, requestor ? ast_channel_linkedid(requestor) : NULL);
00471 console_pvt_unlock(pvt);
00472
00473 if (!chan)
00474 ast_log(LOG_WARNING, "Unable to create new Console channel!\n");
00475
00476 return_unref:
00477 unref_pvt(pvt);
00478
00479 return chan;
00480 }
00481
00482 static int console_digit_begin(struct ast_channel *c, char digit)
00483 {
00484 ast_verb(1, V_BEGIN "Console Received Beginning of Digit %c" V_END, digit);
00485
00486 return -1;
00487 }
00488
00489 static int console_digit_end(struct ast_channel *c, char digit, unsigned int duration)
00490 {
00491 ast_verb(1, V_BEGIN "Console Received End of Digit %c (duration %u)" V_END,
00492 digit, duration);
00493
00494 return -1;
00495 }
00496
00497 static int console_text(struct ast_channel *c, const char *text)
00498 {
00499 ast_verb(1, V_BEGIN "Console Received Text '%s'" V_END, text);
00500
00501 return 0;
00502 }
00503
00504 static int console_hangup(struct ast_channel *c)
00505 {
00506 struct console_pvt *pvt = ast_channel_tech_pvt(c);
00507
00508 ast_verb(1, V_BEGIN "Hangup on Console" V_END);
00509
00510 pvt->hookstate = 0;
00511 pvt->owner = NULL;
00512 stop_stream(pvt);
00513
00514 ast_channel_tech_pvt_set(c, unref_pvt(pvt));
00515
00516 return 0;
00517 }
00518
00519 static int console_answer(struct ast_channel *c)
00520 {
00521 struct console_pvt *pvt = ast_channel_tech_pvt(c);
00522
00523 ast_verb(1, V_BEGIN "Call from Console has been Answered" V_END);
00524
00525 ast_setstate(c, AST_STATE_UP);
00526
00527 return start_stream(pvt);
00528 }
00529
00530
00531
00532
00533
00534
00535
00536
00537
00538
00539
00540
00541
00542
00543
00544
00545
00546
00547
00548
00549
00550 static struct ast_frame *console_read(struct ast_channel *chan)
00551 {
00552 ast_debug(1, "I should not be called ...\n");
00553
00554 return &ast_null_frame;
00555 }
00556
00557 static int console_call(struct ast_channel *c, const char *dest, int timeout)
00558 {
00559 struct console_pvt *pvt = ast_channel_tech_pvt(c);
00560 enum ast_control_frame_type ctrl;
00561
00562 ast_verb(1, V_BEGIN "Call to device '%s' on console from '%s' <%s>" V_END,
00563 dest,
00564 S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, ""),
00565 S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, ""));
00566
00567 console_pvt_lock(pvt);
00568
00569 if (pvt->autoanswer) {
00570 pvt->hookstate = 1;
00571 console_pvt_unlock(pvt);
00572 ast_verb(1, V_BEGIN "Auto-answered" V_END);
00573 ctrl = AST_CONTROL_ANSWER;
00574 } else {
00575 console_pvt_unlock(pvt);
00576 ast_verb(1, V_BEGIN "Type 'console answer' to answer, or use the 'autoanswer' option "
00577 "for future calls" V_END);
00578 ctrl = AST_CONTROL_RINGING;
00579 ast_indicate(c, AST_CONTROL_RINGING);
00580 }
00581
00582 ast_queue_control(c, ctrl);
00583
00584 return start_stream(pvt);
00585 }
00586
00587 static int console_write(struct ast_channel *chan, struct ast_frame *f)
00588 {
00589 struct console_pvt *pvt = ast_channel_tech_pvt(chan);
00590
00591 Pa_WriteStream(pvt->stream, f->data.ptr, f->samples);
00592
00593 return 0;
00594 }
00595
00596 static int console_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen)
00597 {
00598 struct console_pvt *pvt = ast_channel_tech_pvt(chan);
00599 int res = 0;
00600
00601 switch (cond) {
00602 case AST_CONTROL_BUSY:
00603 case AST_CONTROL_CONGESTION:
00604 case AST_CONTROL_RINGING:
00605 case AST_CONTROL_INCOMPLETE:
00606 case AST_CONTROL_PVT_CAUSE_CODE:
00607 case -1:
00608 res = -1;
00609 break;
00610 case AST_CONTROL_PROGRESS:
00611 case AST_CONTROL_PROCEEDING:
00612 case AST_CONTROL_VIDUPDATE:
00613 case AST_CONTROL_SRCUPDATE:
00614 break;
00615 case AST_CONTROL_HOLD:
00616 ast_verb(1, V_BEGIN "Console Has Been Placed on Hold" V_END);
00617 ast_moh_start(chan, data, pvt->mohinterpret);
00618 break;
00619 case AST_CONTROL_UNHOLD:
00620 ast_verb(1, V_BEGIN "Console Has Been Retrieved from Hold" V_END);
00621 ast_moh_stop(chan);
00622 break;
00623 default:
00624 ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n",
00625 cond, ast_channel_name(chan));
00626
00627 res = -1;
00628 }
00629
00630 return res;
00631 }
00632
00633 static int console_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
00634 {
00635 struct console_pvt *pvt = ast_channel_tech_pvt(newchan);
00636
00637 pvt->owner = newchan;
00638
00639 return 0;
00640 }
00641
00642
00643
00644
00645
00646
00647
00648
00649
00650
00651
00652
00653 static char *ast_ext_ctx(struct console_pvt *pvt, const char *src, char **ext, char **ctx)
00654 {
00655 if (ext == NULL || ctx == NULL)
00656 return NULL;
00657
00658 *ext = *ctx = NULL;
00659
00660 if (src && *src != '\0')
00661 *ext = ast_strdup(src);
00662
00663 if (*ext == NULL)
00664 return NULL;
00665
00666 if (!pvt->overridecontext) {
00667
00668 *ctx = strrchr(*ext, '@');
00669 if (*ctx)
00670 *(*ctx)++ = '\0';
00671 }
00672
00673 return *ext;
00674 }
00675
00676 static struct console_pvt *get_active_pvt(void)
00677 {
00678 struct console_pvt *pvt;
00679
00680 ast_rwlock_rdlock(&active_lock);
00681 pvt = ref_pvt(active_pvt);
00682 ast_rwlock_unlock(&active_lock);
00683
00684 return pvt;
00685 }
00686
00687 static char *cli_console_autoanswer(struct ast_cli_entry *e, int cmd,
00688 struct ast_cli_args *a)
00689 {
00690 struct console_pvt *pvt = get_active_pvt();
00691 char *res = CLI_SUCCESS;
00692
00693 switch (cmd) {
00694 case CLI_INIT:
00695 e->command = "console {set|show} autoanswer [on|off]";
00696 e->usage =
00697 "Usage: console {set|show} autoanswer [on|off]\n"
00698 " Enables or disables autoanswer feature. If used without\n"
00699 " argument, displays the current on/off status of autoanswer.\n"
00700 " The default value of autoanswer is in 'oss.conf'.\n";
00701 return NULL;
00702
00703 case CLI_GENERATE:
00704 return NULL;
00705 }
00706
00707 if (!pvt) {
00708 ast_cli(a->fd, "No console device is set as active.\n");
00709 return CLI_FAILURE;
00710 }
00711
00712 if (a->argc == e->args - 1) {
00713 ast_cli(a->fd, "Auto answer is %s.\n", pvt->autoanswer ? "on" : "off");
00714 unref_pvt(pvt);
00715 return CLI_SUCCESS;
00716 }
00717
00718 if (a->argc != e->args) {
00719 unref_pvt(pvt);
00720 return CLI_SHOWUSAGE;
00721 }
00722
00723 if (!strcasecmp(a->argv[e->args-1], "on"))
00724 pvt->autoanswer = 1;
00725 else if (!strcasecmp(a->argv[e->args - 1], "off"))
00726 pvt->autoanswer = 0;
00727 else
00728 res = CLI_SHOWUSAGE;
00729
00730 unref_pvt(pvt);
00731
00732 return res;
00733 }
00734
00735 static char *cli_console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00736 {
00737 struct console_pvt *pvt = get_active_pvt();
00738
00739 if (cmd == CLI_INIT) {
00740 e->command = "console flash";
00741 e->usage =
00742 "Usage: console flash\n"
00743 " Flashes the call currently placed on the console.\n";
00744 return NULL;
00745 } else if (cmd == CLI_GENERATE)
00746 return NULL;
00747
00748 if (!pvt) {
00749 ast_cli(a->fd, "No console device is set as active\n");
00750 return CLI_FAILURE;
00751 }
00752
00753 if (a->argc != e->args)
00754 return CLI_SHOWUSAGE;
00755
00756 if (!pvt->owner) {
00757 ast_cli(a->fd, "No call to flash\n");
00758 unref_pvt(pvt);
00759 return CLI_FAILURE;
00760 }
00761
00762 pvt->hookstate = 0;
00763
00764 ast_queue_control(pvt->owner, AST_CONTROL_FLASH);
00765
00766 unref_pvt(pvt);
00767
00768 return CLI_SUCCESS;
00769 }
00770
00771 static char *cli_console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00772 {
00773 char *s = NULL;
00774 const char *mye = NULL, *myc = NULL;
00775 struct console_pvt *pvt = get_active_pvt();
00776
00777 if (cmd == CLI_INIT) {
00778 e->command = "console dial";
00779 e->usage =
00780 "Usage: console dial [extension[@context]]\n"
00781 " Dials a given extension (and context if specified)\n";
00782 return NULL;
00783 } else if (cmd == CLI_GENERATE)
00784 return NULL;
00785
00786 if (!pvt) {
00787 ast_cli(a->fd, "No console device is currently set as active\n");
00788 return CLI_FAILURE;
00789 }
00790
00791 if (a->argc > e->args + 1)
00792 return CLI_SHOWUSAGE;
00793
00794 if (pvt->owner) {
00795 int i;
00796 struct ast_frame f = { AST_FRAME_DTMF };
00797 const char *s;
00798
00799 if (a->argc == e->args) {
00800 ast_cli(a->fd, "Already in a call. You can only dial digits until you hangup.\n");
00801 unref_pvt(pvt);
00802 return CLI_FAILURE;
00803 }
00804 s = a->argv[e->args];
00805
00806 for (i = 0; i < strlen(s); i++) {
00807 f.subclass.integer = s[i];
00808 ast_queue_frame(pvt->owner, &f);
00809 }
00810 unref_pvt(pvt);
00811 return CLI_SUCCESS;
00812 }
00813
00814
00815 if (a->argc == e->args + 1) {
00816 char *ext = NULL, *con = NULL;
00817 s = ast_ext_ctx(pvt, a->argv[e->args], &ext, &con);
00818 ast_debug(1, "provided '%s', exten '%s' context '%s'\n",
00819 a->argv[e->args], mye, myc);
00820 mye = ext;
00821 myc = con;
00822 }
00823
00824
00825 if (ast_strlen_zero(mye))
00826 mye = pvt->exten;
00827 if (ast_strlen_zero(myc))
00828 myc = pvt->context;
00829
00830 if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
00831 console_pvt_lock(pvt);
00832 pvt->hookstate = 1;
00833 console_new(pvt, mye, myc, AST_STATE_RINGING, NULL);
00834 console_pvt_unlock(pvt);
00835 } else
00836 ast_cli(a->fd, "No such extension '%s' in context '%s'\n", mye, myc);
00837
00838 free(s);
00839
00840 unref_pvt(pvt);
00841
00842 return CLI_SUCCESS;
00843 }
00844
00845 static char *cli_console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00846 {
00847 struct console_pvt *pvt = get_active_pvt();
00848
00849 if (cmd == CLI_INIT) {
00850 e->command = "console hangup";
00851 e->usage =
00852 "Usage: console hangup\n"
00853 " Hangs up any call currently placed on the console.\n";
00854 return NULL;
00855 } else if (cmd == CLI_GENERATE)
00856 return NULL;
00857
00858 if (!pvt) {
00859 ast_cli(a->fd, "No console device is set as active\n");
00860 return CLI_FAILURE;
00861 }
00862
00863 if (a->argc != e->args)
00864 return CLI_SHOWUSAGE;
00865
00866 if (!pvt->owner && !pvt->hookstate) {
00867 ast_cli(a->fd, "No call to hang up\n");
00868 unref_pvt(pvt);
00869 return CLI_FAILURE;
00870 }
00871
00872 pvt->hookstate = 0;
00873 if (pvt->owner)
00874 ast_queue_hangup(pvt->owner);
00875
00876 unref_pvt(pvt);
00877
00878 return CLI_SUCCESS;
00879 }
00880
00881 static char *cli_console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00882 {
00883 const char *s;
00884 struct console_pvt *pvt = get_active_pvt();
00885 char *res = CLI_SUCCESS;
00886
00887 if (cmd == CLI_INIT) {
00888 e->command = "console {mute|unmute}";
00889 e->usage =
00890 "Usage: console {mute|unmute}\n"
00891 " Mute/unmute the microphone.\n";
00892 return NULL;
00893 } else if (cmd == CLI_GENERATE)
00894 return NULL;
00895
00896 if (!pvt) {
00897 ast_cli(a->fd, "No console device is set as active\n");
00898 return CLI_FAILURE;
00899 }
00900
00901 if (a->argc != e->args)
00902 return CLI_SHOWUSAGE;
00903
00904 s = a->argv[e->args-1];
00905 if (!strcasecmp(s, "mute"))
00906 pvt->muted = 1;
00907 else if (!strcasecmp(s, "unmute"))
00908 pvt->muted = 0;
00909 else
00910 res = CLI_SHOWUSAGE;
00911
00912 ast_verb(1, V_BEGIN "The Console is now %s" V_END,
00913 pvt->muted ? "Muted" : "Unmuted");
00914
00915 unref_pvt(pvt);
00916
00917 return res;
00918 }
00919
00920 static char *cli_list_available(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00921 {
00922 PaDeviceIndex idx, num, def_input, def_output;
00923
00924 if (cmd == CLI_INIT) {
00925 e->command = "console list available";
00926 e->usage =
00927 "Usage: console list available\n"
00928 " List all available devices.\n";
00929 return NULL;
00930 } else if (cmd == CLI_GENERATE)
00931 return NULL;
00932
00933 if (a->argc != e->args)
00934 return CLI_SHOWUSAGE;
00935
00936 ast_cli(a->fd, "\n"
00937 "=============================================================\n"
00938 "=== Available Devices =======================================\n"
00939 "=============================================================\n"
00940 "===\n");
00941
00942 num = Pa_GetDeviceCount();
00943 if (!num) {
00944 ast_cli(a->fd, "(None)\n");
00945 return CLI_SUCCESS;
00946 }
00947
00948 def_input = Pa_GetDefaultInputDevice();
00949 def_output = Pa_GetDefaultOutputDevice();
00950 for (idx = 0; idx < num; idx++) {
00951 const PaDeviceInfo *dev = Pa_GetDeviceInfo(idx);
00952 if (!dev)
00953 continue;
00954 ast_cli(a->fd, "=== ---------------------------------------------------------\n"
00955 "=== Device Name: %s\n", dev->name);
00956 if (dev->maxInputChannels)
00957 ast_cli(a->fd, "=== ---> %sInput Device\n", (idx == def_input) ? "Default " : "");
00958 if (dev->maxOutputChannels)
00959 ast_cli(a->fd, "=== ---> %sOutput Device\n", (idx == def_output) ? "Default " : "");
00960 ast_cli(a->fd, "=== ---------------------------------------------------------\n===\n");
00961 }
00962
00963 ast_cli(a->fd, "=============================================================\n\n");
00964
00965 return CLI_SUCCESS;
00966 }
00967
00968 static char *cli_list_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00969 {
00970 struct ao2_iterator i;
00971 struct console_pvt *pvt;
00972
00973 if (cmd == CLI_INIT) {
00974 e->command = "console list devices";
00975 e->usage =
00976 "Usage: console list devices\n"
00977 " List all configured devices.\n";
00978 return NULL;
00979 } else if (cmd == CLI_GENERATE)
00980 return NULL;
00981
00982 if (a->argc != e->args)
00983 return CLI_SHOWUSAGE;
00984
00985 ast_cli(a->fd, "\n"
00986 "=============================================================\n"
00987 "=== Configured Devices ======================================\n"
00988 "=============================================================\n"
00989 "===\n");
00990
00991 i = ao2_iterator_init(pvts, 0);
00992 while ((pvt = ao2_iterator_next(&i))) {
00993 console_pvt_lock(pvt);
00994
00995 ast_cli(a->fd, "=== ---------------------------------------------------------\n"
00996 "=== Device Name: %s\n"
00997 "=== ---> Active: %s\n"
00998 "=== ---> Input Device: %s\n"
00999 "=== ---> Output Device: %s\n"
01000 "=== ---> Context: %s\n"
01001 "=== ---> Extension: %s\n"
01002 "=== ---> CallerID Num: %s\n"
01003 "=== ---> CallerID Name: %s\n"
01004 "=== ---> MOH Interpret: %s\n"
01005 "=== ---> Language: %s\n"
01006 "=== ---> Parkinglot: %s\n"
01007 "=== ---> Muted: %s\n"
01008 "=== ---> Auto-Answer: %s\n"
01009 "=== ---> Override Context: %s\n"
01010 "=== ---------------------------------------------------------\n===\n",
01011 pvt->name, (pvt == active_pvt) ? "Yes" : "No",
01012 pvt->input_device, pvt->output_device, pvt->context,
01013 pvt->exten, pvt->cid_num, pvt->cid_name, pvt->mohinterpret,
01014 pvt->language, pvt->parkinglot, pvt->muted ? "Yes" : "No", pvt->autoanswer ? "Yes" : "No",
01015 pvt->overridecontext ? "Yes" : "No");
01016
01017 console_pvt_unlock(pvt);
01018 unref_pvt(pvt);
01019 }
01020 ao2_iterator_destroy(&i);
01021
01022 ast_cli(a->fd, "=============================================================\n\n");
01023
01024 return CLI_SUCCESS;
01025 }
01026
01027
01028
01029 static char *cli_console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01030 {
01031 struct console_pvt *pvt = get_active_pvt();
01032
01033 switch (cmd) {
01034 case CLI_INIT:
01035 e->command = "console answer";
01036 e->usage =
01037 "Usage: console answer\n"
01038 " Answers an incoming call on the console channel.\n";
01039 return NULL;
01040
01041 case CLI_GENERATE:
01042 return NULL;
01043 }
01044
01045 if (!pvt) {
01046 ast_cli(a->fd, "No console device is set as active\n");
01047 return CLI_FAILURE;
01048 }
01049
01050 if (a->argc != e->args) {
01051 unref_pvt(pvt);
01052 return CLI_SHOWUSAGE;
01053 }
01054
01055 if (!pvt->owner) {
01056 ast_cli(a->fd, "No one is calling us\n");
01057 unref_pvt(pvt);
01058 return CLI_FAILURE;
01059 }
01060
01061 pvt->hookstate = 1;
01062
01063 ast_indicate(pvt->owner, -1);
01064
01065 ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
01066
01067 unref_pvt(pvt);
01068
01069 return CLI_SUCCESS;
01070 }
01071
01072
01073
01074
01075
01076
01077
01078 static char *cli_console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01079 {
01080 char buf[TEXT_SIZE];
01081 struct console_pvt *pvt = get_active_pvt();
01082 struct ast_frame f = {
01083 .frametype = AST_FRAME_TEXT,
01084 .data.ptr = buf,
01085 .src = "console_send_text",
01086 };
01087 int len;
01088
01089 if (cmd == CLI_INIT) {
01090 e->command = "console send text";
01091 e->usage =
01092 "Usage: console send text <message>\n"
01093 " Sends a text message for display on the remote terminal.\n";
01094 return NULL;
01095 } else if (cmd == CLI_GENERATE)
01096 return NULL;
01097
01098 if (!pvt) {
01099 ast_cli(a->fd, "No console device is set as active\n");
01100 return CLI_FAILURE;
01101 }
01102
01103 if (a->argc < e->args + 1) {
01104 unref_pvt(pvt);
01105 return CLI_SHOWUSAGE;
01106 }
01107
01108 if (!pvt->owner) {
01109 ast_cli(a->fd, "Not in a call\n");
01110 unref_pvt(pvt);
01111 return CLI_FAILURE;
01112 }
01113
01114 ast_join(buf, sizeof(buf) - 1, a->argv + e->args);
01115 if (ast_strlen_zero(buf)) {
01116 unref_pvt(pvt);
01117 return CLI_SHOWUSAGE;
01118 }
01119
01120 len = strlen(buf);
01121 buf[len] = '\n';
01122 f.datalen = len + 1;
01123
01124 ast_queue_frame(pvt->owner, &f);
01125
01126 unref_pvt(pvt);
01127
01128 return CLI_SUCCESS;
01129 }
01130
01131 static void set_active(struct console_pvt *pvt, const char *value)
01132 {
01133 if (pvt == &globals) {
01134 ast_log(LOG_ERROR, "active is only valid as a per-device setting\n");
01135 return;
01136 }
01137
01138 if (!ast_true(value))
01139 return;
01140
01141 ast_rwlock_wrlock(&active_lock);
01142 if (active_pvt)
01143 unref_pvt(active_pvt);
01144 active_pvt = ref_pvt(pvt);
01145 ast_rwlock_unlock(&active_lock);
01146 }
01147
01148 static char *cli_console_active(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01149 {
01150 struct console_pvt *pvt;
01151
01152 switch (cmd) {
01153 case CLI_INIT:
01154 e->command = "console {set|show} active";
01155 e->usage =
01156 "Usage: console {set|show} active [<device>]\n"
01157 " Set or show the active console device for the Asterisk CLI.\n";
01158 return NULL;
01159 case CLI_GENERATE:
01160 if (a->pos == e->args) {
01161 struct ao2_iterator i;
01162 int x = 0;
01163 char *res = NULL;
01164 i = ao2_iterator_init(pvts, 0);
01165 while ((pvt = ao2_iterator_next(&i))) {
01166 if (++x > a->n && !strncasecmp(pvt->name, a->word, strlen(a->word)))
01167 res = ast_strdup(pvt->name);
01168 unref_pvt(pvt);
01169 if (res) {
01170 ao2_iterator_destroy(&i);
01171 return res;
01172 }
01173 }
01174 ao2_iterator_destroy(&i);
01175 }
01176 return NULL;
01177 }
01178
01179 if (a->argc < e->args)
01180 return CLI_SHOWUSAGE;
01181
01182 if (a->argc == 3) {
01183 pvt = get_active_pvt();
01184
01185 if (!pvt)
01186 ast_cli(a->fd, "No device is currently set as the active console device.\n");
01187 else {
01188 console_pvt_lock(pvt);
01189 ast_cli(a->fd, "The active console device is '%s'.\n", pvt->name);
01190 console_pvt_unlock(pvt);
01191 pvt = unref_pvt(pvt);
01192 }
01193
01194 return CLI_SUCCESS;
01195 }
01196
01197 if (!(pvt = find_pvt(a->argv[e->args - 1]))) {
01198 ast_cli(a->fd, "Could not find a device called '%s'.\n", a->argv[e->args]);
01199 return CLI_FAILURE;
01200 }
01201
01202 set_active(pvt, "yes");
01203
01204 console_pvt_lock(pvt);
01205 ast_cli(a->fd, "The active console device has been set to '%s'\n", pvt->name);
01206 console_pvt_unlock(pvt);
01207
01208 unref_pvt(pvt);
01209
01210 return CLI_SUCCESS;
01211 }
01212
01213 static struct ast_cli_entry cli_console[] = {
01214 AST_CLI_DEFINE(cli_console_dial, "Dial an extension from the console"),
01215 AST_CLI_DEFINE(cli_console_hangup, "Hangup a call on the console"),
01216 AST_CLI_DEFINE(cli_console_mute, "Disable/Enable mic input"),
01217 AST_CLI_DEFINE(cli_console_answer, "Answer an incoming console call"),
01218 AST_CLI_DEFINE(cli_console_sendtext, "Send text to a connected party"),
01219 AST_CLI_DEFINE(cli_console_flash, "Send a flash to the connected party"),
01220 AST_CLI_DEFINE(cli_console_autoanswer, "Turn autoanswer on or off"),
01221 AST_CLI_DEFINE(cli_list_available, "List available devices"),
01222 AST_CLI_DEFINE(cli_list_devices, "List configured devices"),
01223 AST_CLI_DEFINE(cli_console_active, "View or Set the active console device"),
01224 };
01225
01226
01227
01228
01229
01230
01231 static void set_pvt_defaults(struct console_pvt *pvt)
01232 {
01233 if (pvt == &globals) {
01234 ast_string_field_set(pvt, mohinterpret, "default");
01235 ast_string_field_set(pvt, context, "default");
01236 ast_string_field_set(pvt, exten, "s");
01237 ast_string_field_set(pvt, language, "");
01238 ast_string_field_set(pvt, cid_num, "");
01239 ast_string_field_set(pvt, cid_name, "");
01240 ast_string_field_set(pvt, parkinglot, "");
01241
01242 pvt->overridecontext = 0;
01243 pvt->autoanswer = 0;
01244 } else {
01245 ast_mutex_lock(&globals_lock);
01246
01247 ast_string_field_set(pvt, mohinterpret, globals.mohinterpret);
01248 ast_string_field_set(pvt, context, globals.context);
01249 ast_string_field_set(pvt, exten, globals.exten);
01250 ast_string_field_set(pvt, language, globals.language);
01251 ast_string_field_set(pvt, cid_num, globals.cid_num);
01252 ast_string_field_set(pvt, cid_name, globals.cid_name);
01253 ast_string_field_set(pvt, parkinglot, globals.parkinglot);
01254
01255 pvt->overridecontext = globals.overridecontext;
01256 pvt->autoanswer = globals.autoanswer;
01257
01258 ast_mutex_unlock(&globals_lock);
01259 }
01260 }
01261
01262 static void store_callerid(struct console_pvt *pvt, const char *value)
01263 {
01264 char cid_name[256];
01265 char cid_num[256];
01266
01267 ast_callerid_split(value, cid_name, sizeof(cid_name),
01268 cid_num, sizeof(cid_num));
01269
01270 ast_string_field_set(pvt, cid_name, cid_name);
01271 ast_string_field_set(pvt, cid_num, cid_num);
01272 }
01273
01274
01275
01276
01277
01278
01279 static void store_config_core(struct console_pvt *pvt, const char *var, const char *value)
01280 {
01281 if (pvt == &globals && !ast_jb_read_conf(&global_jbconf, var, value))
01282 return;
01283
01284 CV_START(var, value);
01285
01286 CV_STRFIELD("context", pvt, context);
01287 CV_STRFIELD("extension", pvt, exten);
01288 CV_STRFIELD("mohinterpret", pvt, mohinterpret);
01289 CV_STRFIELD("language", pvt, language);
01290 CV_F("callerid", store_callerid(pvt, value));
01291 CV_BOOL("overridecontext", pvt->overridecontext);
01292 CV_BOOL("autoanswer", pvt->autoanswer);
01293 CV_STRFIELD("parkinglot", pvt, parkinglot);
01294
01295 if (pvt != &globals) {
01296 CV_F("active", set_active(pvt, value))
01297 CV_STRFIELD("input_device", pvt, input_device);
01298 CV_STRFIELD("output_device", pvt, output_device);
01299 }
01300
01301 ast_log(LOG_WARNING, "Unknown option '%s'\n", var);
01302
01303 CV_END;
01304 }
01305
01306 static void pvt_destructor(void *obj)
01307 {
01308 struct console_pvt *pvt = obj;
01309
01310 ast_string_field_free_memory(pvt);
01311 }
01312
01313 static int init_pvt(struct console_pvt *pvt, const char *name)
01314 {
01315 pvt->thread = AST_PTHREADT_NULL;
01316
01317 if (ast_string_field_init(pvt, 32))
01318 return -1;
01319
01320 ast_string_field_set(pvt, name, S_OR(name, ""));
01321
01322 return 0;
01323 }
01324
01325 static void build_device(struct ast_config *cfg, const char *name)
01326 {
01327 struct ast_variable *v;
01328 struct console_pvt *pvt;
01329 int new = 0;
01330
01331 if ((pvt = find_pvt(name))) {
01332 console_pvt_lock(pvt);
01333 set_pvt_defaults(pvt);
01334 pvt->destroy = 0;
01335 } else {
01336 if (!(pvt = ao2_alloc(sizeof(*pvt), pvt_destructor)))
01337 return;
01338 init_pvt(pvt, name);
01339 set_pvt_defaults(pvt);
01340 new = 1;
01341 }
01342
01343 for (v = ast_variable_browse(cfg, name); v; v = v->next)
01344 store_config_core(pvt, v->name, v->value);
01345
01346 if (new)
01347 ao2_link(pvts, pvt);
01348 else
01349 console_pvt_unlock(pvt);
01350
01351 unref_pvt(pvt);
01352 }
01353
01354 static int pvt_mark_destroy_cb(void *obj, void *arg, int flags)
01355 {
01356 struct console_pvt *pvt = obj;
01357 pvt->destroy = 1;
01358 return 0;
01359 }
01360
01361 static void destroy_pvts(void)
01362 {
01363 struct ao2_iterator i;
01364 struct console_pvt *pvt;
01365
01366 i = ao2_iterator_init(pvts, 0);
01367 while ((pvt = ao2_iterator_next(&i))) {
01368 if (pvt->destroy) {
01369 ao2_unlink(pvts, pvt);
01370 ast_rwlock_wrlock(&active_lock);
01371 if (active_pvt == pvt)
01372 active_pvt = unref_pvt(pvt);
01373 ast_rwlock_unlock(&active_lock);
01374 }
01375 unref_pvt(pvt);
01376 }
01377 ao2_iterator_destroy(&i);
01378 }
01379
01380
01381
01382
01383
01384
01385
01386 static int load_config(int reload)
01387 {
01388 struct ast_config *cfg;
01389 struct ast_variable *v;
01390 struct ast_flags config_flags = { 0 };
01391 char *context = NULL;
01392
01393
01394 memcpy(&global_jbconf, &default_jbconf, sizeof(global_jbconf));
01395 ast_mutex_lock(&globals_lock);
01396 set_pvt_defaults(&globals);
01397 ast_mutex_unlock(&globals_lock);
01398
01399 if (!(cfg = ast_config_load(config_file, config_flags))) {
01400 ast_log(LOG_NOTICE, "Unable to open configuration file %s!\n", config_file);
01401 return -1;
01402 } else if (cfg == CONFIG_STATUS_FILEINVALID) {
01403 ast_log(LOG_NOTICE, "Config file %s has an invalid format\n", config_file);
01404 return -1;
01405 }
01406
01407 ao2_callback(pvts, OBJ_NODATA, pvt_mark_destroy_cb, NULL);
01408
01409 ast_mutex_lock(&globals_lock);
01410 for (v = ast_variable_browse(cfg, "general"); v; v = v->next)
01411 store_config_core(&globals, v->name, v->value);
01412 ast_mutex_unlock(&globals_lock);
01413
01414 while ((context = ast_category_browse(cfg, context))) {
01415 if (strcasecmp(context, "general"))
01416 build_device(cfg, context);
01417 }
01418
01419 ast_config_destroy(cfg);
01420
01421 destroy_pvts();
01422
01423 return 0;
01424 }
01425
01426 static int pvt_hash_cb(const void *obj, const int flags)
01427 {
01428 const struct console_pvt *pvt = obj;
01429
01430 return ast_str_case_hash(pvt->name);
01431 }
01432
01433 static int pvt_cmp_cb(void *obj, void *arg, int flags)
01434 {
01435 struct console_pvt *pvt = obj, *pvt2 = arg;
01436
01437 return !strcasecmp(pvt->name, pvt2->name) ? CMP_MATCH | CMP_STOP : 0;
01438 }
01439
01440 static void stop_streams(void)
01441 {
01442 struct console_pvt *pvt;
01443 struct ao2_iterator i;
01444
01445 i = ao2_iterator_init(pvts, 0);
01446 while ((pvt = ao2_iterator_next(&i))) {
01447 if (pvt->hookstate)
01448 stop_stream(pvt);
01449 unref_pvt(pvt);
01450 }
01451 ao2_iterator_destroy(&i);
01452 }
01453
01454 static int unload_module(void)
01455 {
01456 console_tech.capabilities = ast_format_cap_destroy(console_tech.capabilities);
01457 ast_channel_unregister(&console_tech);
01458 ast_cli_unregister_multiple(cli_console, ARRAY_LEN(cli_console));
01459
01460 stop_streams();
01461
01462 Pa_Terminate();
01463
01464
01465 ao2_ref(pvts, -1);
01466
01467 pvt_destructor(&globals);
01468
01469 return 0;
01470 }
01471
01472 static int load_module(void)
01473 {
01474 struct ast_format tmpfmt;
01475 PaError res;
01476
01477 if (!(console_tech.capabilities = ast_format_cap_alloc())) {
01478 return AST_MODULE_LOAD_DECLINE;
01479 }
01480 ast_format_cap_add(console_tech.capabilities, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR16, 0));
01481
01482 init_pvt(&globals, NULL);
01483
01484 if (!(pvts = ao2_container_alloc(NUM_PVT_BUCKETS, pvt_hash_cb, pvt_cmp_cb)))
01485 goto return_error;
01486
01487 if (load_config(0))
01488 goto return_error;
01489
01490 res = Pa_Initialize();
01491 if (res != paNoError) {
01492 ast_log(LOG_WARNING, "Failed to initialize audio system - (%d) %s\n",
01493 res, Pa_GetErrorText(res));
01494 goto return_error_pa_init;
01495 }
01496
01497 if (ast_channel_register(&console_tech)) {
01498 ast_log(LOG_ERROR, "Unable to register channel type 'Console'\n");
01499 goto return_error_chan_reg;
01500 }
01501
01502 if (ast_cli_register_multiple(cli_console, ARRAY_LEN(cli_console)))
01503 goto return_error_cli_reg;
01504
01505 return AST_MODULE_LOAD_SUCCESS;
01506
01507 return_error_cli_reg:
01508 ast_cli_unregister_multiple(cli_console, ARRAY_LEN(cli_console));
01509 return_error_chan_reg:
01510 ast_channel_unregister(&console_tech);
01511 return_error_pa_init:
01512 Pa_Terminate();
01513 return_error:
01514 if (pvts)
01515 ao2_ref(pvts, -1);
01516 pvts = NULL;
01517 pvt_destructor(&globals);
01518
01519 return AST_MODULE_LOAD_DECLINE;
01520 }
01521
01522 static int reload(void)
01523 {
01524 return load_config(1);
01525 }
01526
01527 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Console Channel Driver",
01528 .load = load_module,
01529 .unload = unload_module,
01530 .reload = reload,
01531 .load_pri = AST_MODPRI_CHANNEL_DRIVER,
01532 );