summaryrefslogtreecommitdiffhomepage
path: root/src/nomute.c
blob: beb632dff60fabf8952fdb088b33e59aaa3b3ba6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/*
 * Copyright © 2023 Willian Henrique <wsimanbrazil@yahoo.com.br>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

// TODO(linux): this is currently only built on Windows, but a linux
// implementation would be also useful for some games e.g. L4D2

#include "con_.h"
#include "errmsg.h"
#include "feature.h"
#include "hook.h"
#include "intdefs.h"
#include "os.h"
#include "sst.h"

// these have to come after Windows.h (via os.h) and in this order
#include <mmeapi.h>
#include <dsound.h>

FEATURE("inactive window audio control")

DEF_CVAR_UNREG(snd_mute_losefocus,
		"Keep playing audio while tabbed out (SST reimplementation)", 1,
		CON_ARCHIVE | CON_HIDDEN)

static IDirectSoundVtbl *ds_vt = 0;
static typeof(ds_vt->CreateSoundBuffer) orig_CreateSoundBuffer;
static con_cmdcbv1 snd_restart_cb = 0;

// early init (via VDF) happens before config is loaded, and audio is set up
// after that, so we don't want to run snd_restart the first time the cvar is
// set, unless we were loaded later with plugin_load, in which case audio is
// already up and running and we'll want to restart it every time
static bool skiprestart;
static void losefocuscb(struct con_var *v) {
	if (!skiprestart) snd_restart_cb();
	skiprestart = false;
}

static long __stdcall hook_CreateSoundBuffer(IDirectSound *this,
		const DSBUFFERDESC *desc, IDirectSoundBuffer **buff, IUnknown *unk) {
	if (!con_getvari(snd_mute_losefocus)) {
		((DSBUFFERDESC *)desc)->dwFlags |= DSBCAPS_GLOBALFOCUS;
	}
	return orig_CreateSoundBuffer(this, desc, buff, unk);
}

PREINIT {
	if (con_findvar("snd_mute_losefocus")) return false;
	con_reg(snd_mute_losefocus);
	return true;
}

INIT {
	skiprestart = sst_earlyloaded; // see above
	IDirectSound *ds_obj = 0;
	if (DirectSoundCreate(0, &ds_obj, 0) != DS_OK) {
		// XXX: can this error be usefully stringified?
		errmsg_errorx("couldn't create IDirectSound instance");
		return false;
	}
	ds_vt = ds_obj->lpVtbl;
	ds_obj->lpVtbl->Release(ds_obj);
	if (!os_mprot(&ds_vt->CreateSoundBuffer, sizeof(void *), PAGE_READWRITE)) {
		errmsg_errorsys("couldn't make virtual table writable");
		return false;
	}
	orig_CreateSoundBuffer = ds_vt->CreateSoundBuffer;
	ds_vt->CreateSoundBuffer = &hook_CreateSoundBuffer;

	snd_mute_losefocus->base.flags &= ~CON_HIDDEN;
	struct con_cmd *snd_restart = con_findcmd("snd_restart");
	if (snd_restart) {
		snd_restart_cb = con_getcmdcbv1(snd_restart);
		snd_mute_losefocus->cb = &losefocuscb;
	}
	else {
		errmsg_warnx("couldn't find snd_restart");
		errmsg_note("changing snd_mute_losefocus will require changing other "
				"audio settings or restarting the game with SST autoloaded in "
				"order to have an effect");
	}
	return true;
}

END {
	ds_vt->CreateSoundBuffer = orig_CreateSoundBuffer;
}

// vi: sw=4 ts=4 noet tw=80 cc=80