summaryrefslogtreecommitdiff
path: root/volume.cpp
blob: b5858e8f65868e9cad33d3a527de353f37b3e0bd (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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <termios.h>

using namespace std;

int printusage(int,const char *const *argv,const char *message=NULL){
	if(message)cout<<message<<endl;
	cout<<"Usage: "<<argv[0]<<" [volume]"<<endl;
	cout<<"If [volume] is given, changes volume to that value. (Integer in [0,100].)"<<endl;
	cout<<"Otherwise, it shows a graphical slider, adjustable with the arrow keys or h/j/k/l."<<endl;
	cout<<"In graphical mode, 'm' toggles whether sound is muted."<<endl;
	cout<<"If the output is not a tty, the graphical slider is disabled."<<endl;
	return 1;
}

int setvolume(int vol){
	char *cmd=NULL;
	asprintf(&cmd,"osascript -e 'set volume output volume %d'",vol);
	if(!cmd)return 1;
	int ret=system(cmd);
	free(cmd);
	return ret;
}

int getvolume(void){
	FILE *p=popen("osascript -e 'output volume of (get volume settings)'","r");
	if(!p)return 50;
	int vol;
	fscanf(p,"%d",&vol);
	pclose(p);
	return vol;
}

int setmuted(bool m){
	char *cmd=NULL;
	asprintf(&cmd,"osascript -e 'set volume output muted %s'",m?"true":"false");
	if(!cmd)return 1;
	int ret=system(cmd);
	free(cmd);
	return ret;
}

bool getmuted(void){
	FILE *p=popen("osascript -e 'output muted of (get volume settings)'","r");
	if(!p)return false;
	char s[16];
	fscanf(p,"%15s",s);
	pclose(p);
	if(strcmp(s,"true")==0)return true;
	else if(strcmp(s,"false")==0)return false;
	else return false;
}

void displayslider(bool m,int vol){
	cout<<"\x1B[2K\r0 |"
	    <<string(vol/5,'#')<<string((104-vol)/5,'-')
	    <<"| 100 ("<<vol<<"%)";
	if(m)cout<<"  \x1B[1mMUTED (m)\x1B[0m";
	cout.flush();
}

int incvol(int &vol,int inc){
	if((vol==0&&inc<=0)||(vol==100&&inc>=0))return 0;
	vol+=inc;
	if(vol<0)vol=0;
	if(vol>100)vol=100;
	return setvolume(vol);
}

int interact(bool &m,int &vol){
	char c=cin.get();
	if(c=='q'||c=='Q'||c=='\n'||c=='\r')return 1;
	if(c=='l')return incvol(vol,5);
	if(c=='h')return incvol(vol,-5);
	if(c=='k')return incvol(vol,100);
	if(c=='j')return incvol(vol,-100);
	if(c=='m'){
		m=!m;
		setmuted(m);
		return 0;
	}
	if(c!=27){
		cout<<'\x07'<<flush;
		return 0;
	}
	c=cin.get();
	if(c!='['){
		cout<<'\x07'<<flush;
		return 0;
	}
	c=cin.get();
	if(c=='C')return incvol(vol,5);
	if(c=='D')return incvol(vol,-5);
	if(c=='A')return incvol(vol,100);
	if(c=='B')return incvol(vol,-100);
	cout<<'\x07'<<flush;
	return 0;
}

struct termios tios_bak;

void initscreen(void){
	struct termios tios;
	tcgetattr(0,&tios_bak);
	tios=tios_bak;
	tios.c_lflag&=~(
		ECHO|ECHOE|ECHOKE //no echo of normal characters, erasing and killing
		|ECHOCTL //don't visibly echo control characters (^V etc.)
		|ECHONL //don't even echo a newline
		|ICANON //disable canonical mode
#ifdef NOKERNINFO
		|NOKERNINFO //don't print a status line on ^T
#endif
		|IEXTEN //don't handle things like ^V specially
		//|ISIG //disable ^C ^\ and ^Z
		);
	tios.c_cc[VMIN]=1; //read one char at a time
	tios.c_cc[VTIME]=0; //no timeout on reading, make it a blocking read
	tcsetattr(0,TCSAFLUSH,&tios);
}

void endscreen(void){
	tcsetattr(0,TCSAFLUSH,&tios_bak);
}

int main(int argc,char **argv){
	if(argc>2)return printusage(argc,argv);
	if(argc==2){
		if(argv[1][0]=='\0')return printusage(argc,argv,"Invalid volume format");
		char *endp;
		const int vol=strtol(argv[1],&endp,10);
		if(*endp!='\0')return printusage(argc,argv,"Invalid volume format");
		if(vol<0||vol>100)return printusage(argc,argv,"Volume argument not in range");
		return setvolume(vol);
	}
	if(!isatty(1))return printusage(argc,argv,"Won't display slider if stdout is not a terminal");
	initscreen();
	atexit(endscreen);
	bool m=getmuted();
	int vol=getvolume();
	while(true){
		displayslider(m,vol);
		if(interact(m,vol))break;
	}
	cout<<endl;
}