view src/sound.c @ 36438:2eee86dd2af3 draft v9.1.0829

patch 9.1.0829: Vim source code uses a mix of tabs and spaces Commit: https://github.com/vim/vim/commit/8ce738de3fd7192fa6274730594305cde780074c Author: Luca Saccarola <github.e41mv@aleeas.com> Date: Sat Nov 2 16:22:45 2024 +0100 patch 9.1.0829: Vim source code uses a mix of tabs and spaces Problem: Vim source code uses a mix of tabs and spaces Solution: Expand tabs in sound.c, this is an experiment (Luca Saccarola) closes: #15969 Signed-off-by: Luca Saccarola <github.e41mv@aleeas.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Sat, 02 Nov 2024 16:45:02 +0100
parents 385aaea67d33
children
line wrap: on
line source

/* vi:set ts=8 sts=4 sw=4 et:
 *
 * VIM - Vi IMproved    by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 * See README.txt for an overview of the Vim source code.
 */

/*
 * sound.c: functions related making noise
 */

#include "vim.h"

#if defined(FEAT_SOUND) || defined(PROTO)

static long         sound_id = 0;

// soundcb_T is typedef'ed in proto/sound.pro

struct soundcb_S {
    callback_T  snd_callback;
#ifdef MSWIN
    MCIDEVICEID snd_device_id;
    long        snd_id;
#endif
    soundcb_T   *snd_next;
};

static soundcb_T    *first_callback = NULL;

/*
 * Return TRUE when a sound callback has been created, it may be invoked when
 * the sound finishes playing.  Also see has_sound_callback_in_queue().
 */
    int
has_any_sound_callback(void)
{
    return first_callback != NULL;
}

    static soundcb_T *
get_sound_callback(typval_T *arg)
{
    callback_T  callback;
    soundcb_T   *soundcb;

    if (arg->v_type == VAR_UNKNOWN)
        return NULL;
    callback = get_callback(arg);
    if (callback.cb_name == NULL)
        return NULL;

    soundcb = ALLOC_ONE(soundcb_T);
    if (soundcb == NULL)
    {
        free_callback(&callback);
        return NULL;
    }

    soundcb->snd_next = first_callback;
    first_callback = soundcb;
    set_callback(&soundcb->snd_callback, &callback);
    if (callback.cb_free_name)
        vim_free(callback.cb_name);
    return soundcb;
}

/*
 * Call "soundcb" with proper parameters.
 */
    void
call_sound_callback(soundcb_T *soundcb, long snd_id, int result)
{
    typval_T    argv[3];
    typval_T    rettv;

    argv[0].v_type = VAR_NUMBER;
    argv[0].vval.v_number = snd_id;
    argv[1].v_type = VAR_NUMBER;
    argv[1].vval.v_number = result;
    argv[2].v_type = VAR_UNKNOWN;

    call_callback(&soundcb->snd_callback, -1, &rettv, 2, argv);
    clear_tv(&rettv);
}

/*
 * Delete "soundcb" from the list of pending callbacks.
 */
    void
delete_sound_callback(soundcb_T *soundcb)
{
    soundcb_T   *p;
    soundcb_T   *prev = NULL;

    for (p = first_callback; p != NULL; prev = p, p = p->snd_next)
        if (p == soundcb)
        {
            if (prev == NULL)
                first_callback = p->snd_next;
            else
                prev->snd_next = p->snd_next;
            free_callback(&p->snd_callback);
            vim_free(p);
            break;
        }
}

#if defined(HAVE_CANBERRA) || defined(PROTO)

/*
 * Sound implementation for Linux/Unix using libcanberra.
 */
# include <canberra.h>

static ca_context   *context = NULL;

// Structure to store info about a sound callback to be invoked soon.
typedef struct soundcb_queue_S soundcb_queue_T;

struct soundcb_queue_S {
    soundcb_queue_T     *scb_next;
    uint32_t            scb_id;         // ID of the sound
    int                 scb_result;     // CA_ value
    soundcb_T           *scb_callback;  // function to call
};

// Queue of callbacks to invoke from the main loop.
static soundcb_queue_T *callback_queue = NULL;

/*
 * Add a callback to the queue of callbacks to invoke later from the main loop.
 * That is because the callback may be called from another thread and invoking
 * another sound function may cause trouble.
 */
    static void
sound_callback(
        ca_context  *c UNUSED,
        uint32_t    id,
        int         error_code,
        void        *userdata)
{
    soundcb_T       *soundcb = (soundcb_T *)userdata;
    soundcb_queue_T *scb;

    scb = ALLOC_ONE(soundcb_queue_T);
    if (scb == NULL)
        return;
    scb->scb_next = callback_queue;
    callback_queue = scb;
    scb->scb_id = id;
    scb->scb_result = error_code == CA_SUCCESS ? 0
                          : error_code == CA_ERROR_CANCELED
                                            || error_code == CA_ERROR_DESTROYED
                          ? 1 : 2;
    scb->scb_callback = soundcb;
}

/*
 * Return TRUE if there is a sound callback to be called.
 */
    int
has_sound_callback_in_queue(void)
{
    return callback_queue != NULL;
}

/*
 * Invoke queued sound callbacks.
 */
    void
invoke_sound_callback(void)
{
    soundcb_queue_T *scb;

    while (callback_queue != NULL)
    {
        scb = callback_queue;
        callback_queue = scb->scb_next;

        call_sound_callback(scb->scb_callback, scb->scb_id, scb->scb_result);

        delete_sound_callback(scb->scb_callback);
        vim_free(scb);
    }
    redraw_after_callback(TRUE, FALSE);
}

    static void
sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
        return;

    if (context == NULL)
        ca_context_create(&context);
    if (context == NULL)
        return;

    soundcb_T   *soundcb = get_sound_callback(&argvars[1]);
    int         res = CA_ERROR_INVALID;

    ++sound_id;
    if (soundcb == NULL)
    {
        res = ca_context_play(context, sound_id,
                playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
                tv_get_string(&argvars[0]),
                CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
                NULL);
    }
    else
    {
        static ca_proplist *proplist = NULL;

        ca_proplist_create(&proplist);
        if (proplist != NULL)
        {
            if (playfile)
                ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
                        (char *)tv_get_string(&argvars[0]));
            else
                ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
                        (char *)tv_get_string(&argvars[0]));
            ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
                    "volatile");
            res = ca_context_play_full(context, sound_id, proplist,
                    sound_callback, soundcb);
            if (res != CA_SUCCESS)
                delete_sound_callback(soundcb);

            ca_proplist_destroy(proplist);
        }
    }
    rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
}

    void
f_sound_playevent(typval_T *argvars, typval_T *rettv)
{
    sound_play_common(argvars, rettv, FALSE);
}

/*
 * implementation of sound_playfile({path} [, {callback}])
 */
    void
f_sound_playfile(typval_T *argvars, typval_T *rettv)
{
    sound_play_common(argvars, rettv, TRUE);
}

/*
 * implementation of sound_stop({id})
 */
    void
f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
        return;

    if (context != NULL)
        ca_context_cancel(context, tv_get_number(&argvars[0]));
}

/*
 * implementation of sound_clear()
 */
    void
f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    if (context == NULL)
        return;
    ca_context_destroy(context);
    context = NULL;
}

# if defined(EXITFREE) || defined(PROTO)
    void
sound_free(void)
{
    soundcb_queue_T *scb;

    if (context != NULL)
        ca_context_destroy(context);

    while (first_callback != NULL)
        delete_sound_callback(first_callback);

    while (callback_queue != NULL)
    {
        scb = callback_queue;
        callback_queue = scb->scb_next;
        delete_sound_callback(scb->scb_callback);
        vim_free(scb);
    }
}
# endif

#elif defined(MSWIN)

/*
 * Sound implementation for MS-Windows.
 */

static HWND g_hWndSound = NULL;

    static LRESULT CALLBACK
sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    soundcb_T   *p;

    switch (message)
    {
        case MM_MCINOTIFY:
            for (p = first_callback; p != NULL; p = p->snd_next)
                if (p->snd_device_id == (MCIDEVICEID) lParam)
                {
                    char        buf[32];

                    vim_snprintf(buf, sizeof(buf), "close sound%06ld",
                                                                p->snd_id);
                    mciSendStringA(buf, NULL, 0, 0);

                    long result =   wParam == MCI_NOTIFY_SUCCESSFUL ? 0
                                  : wParam == MCI_NOTIFY_ABORTED ? 1 : 2;
                    call_sound_callback(p, p->snd_id, result);

                    delete_sound_callback(p);
                    redraw_after_callback(TRUE, FALSE);

                }
            break;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

    static HWND
sound_window(void)
{
    if (g_hWndSound == NULL)
    {
        LPCSTR clazz = "VimSound";
        WNDCLASS wndclass = {
            0, sound_wndproc, 0, 0, g_hinst, NULL, 0, 0, NULL, clazz };
        RegisterClass(&wndclass);
        g_hWndSound = CreateWindow(clazz, NULL, 0, 0, 0, 0, 0,
                HWND_MESSAGE, NULL, g_hinst, NULL);
    }

    return g_hWndSound;
}

    void
f_sound_playevent(typval_T *argvars, typval_T *rettv)
{
    WCHAR           *wp;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
        return;

    wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL);
    if (wp == NULL)
        return;

    if (PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS))
        rettv->vval.v_number = ++sound_id;
    free(wp);
}

    void
f_sound_playfile(typval_T *argvars, typval_T *rettv)
{
    long        newid = sound_id + 1;
    size_t      len;
    char_u      *p, *filename;
    WCHAR       *wp;
    soundcb_T   *soundcb;
    char        buf[32];
    MCIERROR    err;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
        return;

    filename = tv_get_string(&argvars[0]);

    len = STRLEN(filename) + 5 + 18 + 2 + 1;
    p = alloc(len);
    if (p == NULL)
    {
        return;
    }
    vim_snprintf((char *)p, len, "open \"%s\" alias sound%06ld", filename, newid);

    wp = enc_to_utf16((char_u *)p, NULL);
    free(p);
    if (wp == NULL)
        return;

    err = mciSendStringW(wp, NULL, 0, sound_window());
    free(wp);
    if (err != 0)
        return;

    vim_snprintf(buf, sizeof(buf), "play sound%06ld notify", newid);
    err = mciSendStringA(buf, NULL, 0, sound_window());
    if (err != 0)
        goto failure;

    sound_id = newid;
    rettv->vval.v_number = sound_id;

    soundcb = get_sound_callback(&argvars[1]);
    if (soundcb != NULL)
    {
        vim_snprintf(buf, sizeof(buf), "sound%06ld", newid);
        soundcb->snd_id = newid;
        soundcb->snd_device_id = mciGetDeviceID(buf);
    }
    return;

failure:
    vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
    mciSendStringA(buf, NULL, 0, NULL);
}

    void
f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
{
    long    id;
    char    buf[32];

    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
        return;

    id = tv_get_number(&argvars[0]);
    vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id);
    mciSendStringA(buf, NULL, 0, NULL);
}

    void
f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    PlaySoundW(NULL, NULL, 0);
    mciSendStringA("close all", NULL, 0, NULL);
}

# if defined(EXITFREE)
    void
sound_free(void)
{
    CloseWindow(g_hWndSound);

    while (first_callback != NULL)
        delete_sound_callback(first_callback);
}
# endif

#elif defined(MACOS_X_DARWIN)

// Sound implementation for macOS.
    static void
sound_play_common(typval_T *argvars, typval_T *rettv, bool playfile)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
        return;

    char_u *sound_name = tv_get_string(&argvars[0]);
    soundcb_T *soundcb = get_sound_callback(&argvars[1]);

    ++sound_id;

    bool play_success = sound_mch_play(sound_name, sound_id, soundcb, playfile);
    if (!play_success && soundcb)
    {
        delete_sound_callback(soundcb);
    }
    rettv->vval.v_number = play_success ? sound_id : 0;
}

    void
f_sound_playevent(typval_T *argvars, typval_T *rettv)
{
    sound_play_common(argvars, rettv, false);
}

    void
f_sound_playfile(typval_T *argvars, typval_T *rettv)
{
    sound_play_common(argvars, rettv, true);
}

    void
f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
        return;
    sound_mch_stop(tv_get_number(&argvars[0]));
}

    void
f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    sound_mch_clear();
}

#if defined(EXITFREE) || defined(PROTO)
    void
sound_free(void)
{
    sound_mch_free();
    while (first_callback != NULL)
        delete_sound_callback(first_callback);
}
#endif

#endif // MACOS_X_DARWIN

#endif  // FEAT_SOUND