wmusic: Drop "-1.0" from playerctl pkg-config check.
[dockapps.git] / AlsaMixer.app / Mixer.cc
blob85cfe71ad6bedb8a2821abe5743bc5b96e283d7b
1 //
2 // Mixer.app / AlsaMixer.app
3 //
4 // Copyright (c) 1998-2002 Per Liden
5 // Copyright (C) 2004, Petr Hlavka
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1307,
20 // USA.
23 #include <X11/Xlib.h>
24 #include <iostream>
25 #include <fstream>
26 #include <cstdlib>
27 #include <cstring>
28 #include <csignal>
29 #include "Xpm.h"
30 #include "Mixer.h"
32 #include "AMixer/AMixer.h"
34 #include "pixmaps/main.xpm"
35 #include "pixmaps/button.xpm"
36 #include "pixmaps/mutebutton.xpm"
37 #include "pixmaps/redlight.xpm"
39 #define ROUND_POS(x) (int ((x) + 0.5) > int (x)) ? (int) ((x) + 1) : (int) (x)
41 using namespace std;
43 static const int ButtonX[] = {6, 24, 42};
44 static const char* MixerSources[] = { "Master", "PCM", "CD" };
46 extern Mixer* app;
48 void catchBrokenPipe(int sig)
50 app->saveVolumeSettings();
51 exit(0);
54 int positionToPercent(int position) {
55 return ROUND_POS(100 - (((position - BUTTON_MAX) * 100.0) / (BUTTON_MIN - BUTTON_MAX)));
58 int percentToPosition(int percent) {
59 return ROUND_POS(BUTTON_MIN - (percent * (BUTTON_MIN - BUTTON_MAX)) / 100.0);
62 Mixer::Mixer(int argc, char** argv)
64 XClassHint classHint;
65 XSizeHints sizeHints;
66 XWMHints wmHints;
67 Atom deleteWindow;
68 Xpm* image;
69 char* displayName = NULL;
70 char* card = "default";
72 mError = 0;
73 mInstanceName = INSTANCENAME;
74 mVolumeSource[0] = -1;
75 mVolumeSource[1] = -1;
76 mVolumeSource[2] = -1;
77 mVolumeMute[0] = 0;
78 mVolumeMute[1] = 0;
79 mVolumeMute[2] = 0;
80 mWheelButton = 1;
81 mLabelText = 0;
82 mSettingsFile = 0;
83 mSaveSettings = false;
84 mLoadSettings = false;
85 mCommand = NULL;
87 // Parse command line
88 if (argc>1) {
89 for (int i=1; i<argc; i++) {
90 // Display
91 if (!strcmp(argv[i], "-d")) {
92 checkArgument(argv, argc, i);
93 displayName = argv[i+1];
94 i++;
97 // Sound source
98 else if (!strcmp(argv[i], "-1") || !strcmp(argv[i], "-2") || !strcmp(argv[i], "-3")) {
99 checkArgument(argv, argc, i);
100 MixerSources[argv[i][1] - '1'] = argv[i + 1];
101 i++;
104 // Wheel binding
105 else if (!strcmp(argv[i], "-w")) {
106 checkArgument(argv, argc, i);
107 mWheelButton = atoi(argv[i+1]);
109 if (mWheelButton < 1 || mWheelButton > 3) {
110 cerr << APPNAME << ": invalid wheel binding, must be 1, 2 or 3, not " << argv[i+1] << endl;
111 tryHelp(argv[0]);
112 exit(0);
115 i++;
118 // Label text
119 else if (!strcmp(argv[i], "-l")) {
120 checkArgument(argv, argc, i);
121 mLabelText = argv[i+1];
122 i++;
125 // Save settings on exit
126 else if (!strcmp(argv[i], "-S")) {
127 mSaveSettings = true;
130 // Load settings on startup
131 else if (!strcmp(argv[i], "-L")) {
132 mLoadSettings = true;
135 // Load/Save settings file
136 else if (!strcmp(argv[i], "-f")) {
137 checkArgument(argv, argc, i);
138 mSettingsFile = argv[i+1];
139 i++;
142 // Execute command on middle click
143 else if (!strcmp(argv[i], "-e")) {
144 checkArgument(argv, argc, i);
145 mCommand = argv[i + 1];
146 i++;
149 // Instance name
150 else if (!strcmp(argv[i], "-n")) {
151 checkArgument(argv, argc, i);
152 mInstanceName = argv[i+1];
153 i++;
156 // Version
157 else if (!strcmp(argv[i], "-v")) {
158 cerr << APPNAME << " version " << VERSION << endl;
159 exit(0);
162 // Help
163 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
164 showHelp();
165 exit(0);
168 // card
169 else if (!strcmp(argv[i], "--card")) {
170 card = AMixer::convertIDToCard(argv[i + 1]);
171 if (!card) {
172 cerr << APPNAME << ": invalid card number '" << argv[i + 1] << "'" << endl;
173 tryHelp(argv[0]);
174 exit(0);
176 i++;
179 // device
180 else if (!strcmp(argv[i], "--device")) {
181 card = argv[i + 1];
182 i++;
185 // Unknown option
186 else {
187 cerr << APPNAME << ": invalid option '" << argv[i] << "'" << endl;
188 tryHelp(argv[0]);
189 exit(0);
194 // default settings file
195 if (!mSettingsFile) {
196 char* home = getenv("HOME");
197 if (home) {
198 mSettingsFile = new char[strlen(home) + strlen(SETTINGS) + 1];
199 strcpy(mSettingsFile, home);
200 strcat(mSettingsFile, SETTINGS);
201 } else {
202 cerr << APPNAME << ": $HOME not set, could not find saved settings" << endl;
206 // init mixer
207 aMixer = new AMixer(card);
208 if (!aMixer->opened()) {
209 cerr << APPNAME << ": could not open mixer device for card '" << card << "'" << endl;
210 exit(0);
213 // open mixer sources
214 for (int i = 0; i < 3; i++) {
215 aMixer->attachItem(i, MixerSources[i]);
216 if (!aMixer->itemOK(i))
217 cerr << APPNAME << ": could not select mixer source '" << MixerSources[i] << "'" << endl;
220 // Open display
221 if ((mDisplay = XOpenDisplay(displayName)) == NULL) {
222 cerr << APPNAME << ": could not open display " << displayName << endl;
223 exit(0);
226 // Get root window
227 mRoot = RootWindow(mDisplay, DefaultScreen(mDisplay));
229 // Create windows
230 mAppWin = XCreateSimpleWindow(mDisplay, mRoot, 1, 1, 64, 64, 0, 0, 0);
231 mIconWin = XCreateSimpleWindow(mDisplay, mAppWin, 0, 0, 64, 64, 0, 0, 0);
233 // Set classhint
234 classHint.res_name = mInstanceName;
235 classHint.res_class = CLASSNAME;
236 XSetClassHint(mDisplay, mAppWin, &classHint);
238 // Create delete atom
239 deleteWindow = XInternAtom(mDisplay, "WM_DELETE_WINDOW", False);
240 XSetWMProtocols(mDisplay, mAppWin, &deleteWindow, 1);
241 XSetWMProtocols(mDisplay, mIconWin, &deleteWindow, 1);
243 // Set windowname
244 XStoreName(mDisplay, mAppWin, APPNAME);
245 XSetIconName(mDisplay, mAppWin, APPNAME);
247 // Set sizehints
248 sizeHints.flags= USPosition;
249 sizeHints.x = 0;
250 sizeHints.y = 0;
251 XSetWMNormalHints(mDisplay, mAppWin, &sizeHints);
253 // Set wmhints
254 wmHints.initial_state = WithdrawnState;
255 wmHints.icon_window = mIconWin;
256 wmHints.icon_x = 0;
257 wmHints.icon_y = 0;
258 wmHints.window_group = mAppWin;
259 wmHints.flags = StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
260 XSetWMHints(mDisplay, mAppWin, &wmHints);
262 // Set command
263 XSetCommand(mDisplay, mAppWin, argv, argc);
265 // Set background image
266 image = new Xpm(mDisplay, mRoot, main_xpm);
267 if (mLabelText) {
268 image->drawString(LABEL_X, LABEL_Y, mLabelText);
270 image->setWindowPixmapShaped(mIconWin);
271 delete image;
273 // Create buttons
274 mButton[0] = XCreateSimpleWindow(mDisplay, mIconWin, ButtonX[0], BUTTON_MIN, 5, 5, 0, 0, 0);
275 mButton[1] = XCreateSimpleWindow(mDisplay, mIconWin, ButtonX[1], BUTTON_MIN, 5, 5, 0, 0, 0);
276 mButton[2] = XCreateSimpleWindow(mDisplay, mIconWin, ButtonX[2], BUTTON_MIN, 5, 5, 0, 0, 0);
278 image = new Xpm(mDisplay, mRoot, button_xpm);
279 image->setWindowPixmap(mButton[0]);
280 image->setWindowPixmap(mButton[1]);
281 image->setWindowPixmap(mButton[2]);
282 delete image;
284 XSelectInput(mDisplay, mButton[0], ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
285 XSelectInput(mDisplay, mButton[1], ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
286 XSelectInput(mDisplay, mButton[2], ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
287 XSelectInput(mDisplay, mIconWin, ButtonPressMask);
289 XMapWindow(mDisplay, mButton[0]);
290 XMapWindow(mDisplay, mButton[1]);
291 XMapWindow(mDisplay, mButton[2]);
293 XMapWindow(mDisplay, mIconWin);
294 XMapWindow(mDisplay, mAppWin);
295 XSync(mDisplay, False);
297 // Catch broker pipe signal
298 signal(SIGPIPE, catchBrokenPipe);
300 // Check if error
301 if (mError) {
302 showErrorLed();
303 } else {
304 getVolume();
305 if (mLoadSettings)
306 loadVolumeSettings();
310 void Mixer::tryHelp(char* appname)
312 cerr << "Try `" << appname << " --help' for more information" << endl;
315 void Mixer::showHelp()
317 cerr << APPNAME << " Copyright (c) 1998-2002 by Per Liden (per@fukt.bth.se), Petr Hlavka (xhlavk00@stud.fit.vutbr.cz)" << endl << endl
318 << "options:" << endl
319 << " -1 <source> set sound source for control 1 (default is Master)" << endl
320 << " -2 <source> set sound source for control 2 (default is PCM)" << endl
321 << " -3 <source> set sound source for control 3 (default is CD)" << endl
322 << " -w 1|2|3 bind a control button to the mouse wheel (default is 1)" << endl
323 << " -l <text> set label text" << endl
324 << " -S save volume settings on exit" << endl
325 << " -L load volume settings on start up" << endl
326 << " -f <file> use setting <file> instead of ~/GNUstep/Defaults/AlsaMixer" << endl
327 << " --card <id> select card" << endl
328 << " --device <dev> select device, default 'default'" << endl
329 << " -e <command> execute <command> on middle click" << endl
330 << " -n <name> set client instance name" << endl
331 << " -d <disp> set display" << endl
332 << " -v print version and exit" << endl
333 << " -h, --help display this help and exit" << endl << endl;
336 void Mixer::checkArgument(char** argv, int argc, int index)
338 if (argc-1 < index+1) {
339 cerr << APPNAME << ": option '" << argv[index] << "' requires an argument" << endl;
340 tryHelp(argv[0]);
341 exit(0);
345 void Mixer::showErrorLed()
347 Window led;
348 Xpm* image;
350 led = XCreateSimpleWindow(mDisplay, mIconWin, LED_X, LED_Y, 3, 2, 0, 0, 0);
352 // Set background image
353 image = new Xpm(mDisplay, mRoot, redlight_xpm);
354 image->setWindowPixmap(led);
355 delete image;
357 // Show window
358 XMapWindow(mDisplay, led);
359 mError = 1;
362 void Mixer::loadVolumeSettings()
364 if (mSettingsFile) {
365 ifstream file(mSettingsFile);
366 if (file) {
367 // This could fail if the user has edited the file by hand and destroyed the structure
368 char dummy[1024];
369 file >> dummy; // {
370 file >> dummy; // Volume1
371 file >> dummy; // =
372 file >> mVolume[0];
373 file >> dummy; // ;
375 file >> dummy; // Volume2
376 file >> dummy; // =
377 file >> mVolume[1];
378 file >> dummy; // ;
380 file >> dummy; // Volume3
381 file >> dummy; // =
382 file >> mVolume[2];
384 file.close();
385 for (int i = 0; i < 3; i++) {
386 setVolume(i, mVolume[i]);
387 setButtonPosition(i, percentToPosition(mVolume[i]));
393 void Mixer::saveVolumeSettings()
395 if (mSaveSettings) {
396 ofstream file(mSettingsFile);
397 if (file) {
398 // Files in ~/GNUstep/Defaults/ should follow the property list format
399 file << "{" << endl
400 << " Volume1 = " << mVolumePos[0] << ";" << endl
401 << " Volume2 = " << mVolumePos[1] << ";" << endl
402 << " Volume3 = " << mVolumePos[2] << ";" << endl
403 << "}" << endl;
404 file.close();
405 } else {
406 cerr << APPNAME << ": failed to save volume settings in " << mSettingsFile << endl;
411 void Mixer::getVolume()
413 static int lastVolume[3] = {-1, -1, -1};
414 static int lastVolumeMute[3] = {-1, -1, -1};
416 if (mError) {
417 return;
420 // Read from device
421 for (int i=0; i<3; i++) {
422 mVolume[i] = aMixer->itemGetVolume(i);
423 mVolumeMute[i] = aMixer->itemIsMuted(i);
425 if (lastVolume[i] != mVolume[i]) {
426 int y;
428 // Set button position
429 if (mError) {
430 y = BUTTON_MIN;
431 } else {
432 y = percentToPosition(mVolume[i]);
435 setButtonPosition(i, y);
436 lastVolume[i] = mVolume[i];
439 // set buttom type muted/unmuted
440 if (lastVolumeMute[i] != mVolumeMute[i]) {
441 setButtonType(i);
442 lastVolumeMute[i] = mVolumeMute[i];
446 if (mError) {
447 cerr << APPNAME << ": unable to read from " << mMixerDevice << endl;
448 showErrorLed();
449 return;
453 void Mixer::setVolume(int button, int volume)
455 if (mError) {
456 return;
459 // Store volume
460 mVolume[button] = volume;
462 // Write to device
463 aMixer->itemSetVolume(button, mVolume[button]);
466 void Mixer::toggleMute(int button)
468 aMixer->itemToggleMute(button);
469 mVolumeMute[button] = aMixer->itemIsMuted(button);
470 setButtonType(button);
473 void Mixer::setButtonType(int button)
475 Xpm* image;
477 if (mVolumeMute[button] == 1) { // muted
478 image = new Xpm(mDisplay, mRoot, mutebutton_xpm);
479 image->setWindowPixmap(mButton[button]);
480 delete image;
482 XClearWindow(mDisplay, mButton[button]);
483 } else {
484 image = new Xpm(mDisplay, mRoot, button_xpm);
485 image->setWindowPixmap(mButton[button]);
486 delete image;
488 XClearWindow(mDisplay, mButton[button]);
492 void Mixer::setButtonPosition(int button, int position) {
493 if (position > BUTTON_MIN) {
494 position = BUTTON_MIN;
495 } else if (position < BUTTON_MAX) {
496 position = BUTTON_MAX;
499 XMoveWindow(mDisplay, mButton[button], ButtonX[button], position);
501 mVolumePos[button] = position;
504 void Mixer::setButtonPositionRelative(int button, int relativePosition)
506 int y;
508 // Calc new button position
509 y = mVolumePos[button] + relativePosition;
511 if (y > BUTTON_MIN) {
512 y = BUTTON_MIN;
513 } else if (y < BUTTON_MAX) {
514 y = BUTTON_MAX;
517 // Set button position and volume
518 XMoveWindow(mDisplay, mButton[button], ButtonX[button], y);
520 mVolumePos[button] = y;
522 // set volume
523 setVolume(button, positionToPercent(y));
526 void Mixer::run()
528 XEvent event;
529 int buttonDown = 0;
530 int buttonDownPosition = 0;
532 // Start handling events
533 while(1) {
534 while(XPending(mDisplay) || buttonDown) {
535 XNextEvent(mDisplay, &event);
537 switch(event.type) {
538 case ButtonPress:
539 if (event.xbutton.button == Button4 || event.xbutton.button == Button5) {
540 // Wheel scroll
541 setButtonPositionRelative(mWheelButton - 1, event.xbutton.button == Button5? 3: -3);
542 } else if (event.xbutton.button == Button1 && event.xbutton.window != mIconWin) {
543 // Volume change
544 buttonDown = 1;
545 buttonDownPosition = event.xbutton.y;
546 } else if (event.xbutton.button == Button3 && buttonDown == 0 && event.xbutton.window != mIconWin) {
547 // Mute
548 for (int i=0; i<3; i++) {
549 if (mButton[i] == event.xbutton.window) {
550 toggleMute(i);
551 break;
554 } else if (event.xbutton.button == Button2) {
555 // Load defaults or execute command
556 if (mCommand) {
557 char command[512];
559 snprintf(command, 512, "%s &", mCommand);
560 system(command);
562 else
563 loadVolumeSettings();
565 break;
567 case ButtonRelease:
568 if (event.xbutton.button == Button1) {
569 buttonDown = 0;
571 break;
573 case MotionNotify:
574 if (buttonDown) {
575 // Find button
576 for (int i=0; i<3; i++) {
577 if (mButton[i] == event.xmotion.window) {
578 setButtonPositionRelative(i, event.xmotion.y - buttonDownPosition);
579 break;
583 break;
587 // Idle for a moment
588 usleep(100000);
590 // Update volume status
591 aMixer->handleEvents();
592 if (AMixer::mixerChanged())
593 aMixer->reInit();
594 else if (AMixer::mixerElemsChanged())
595 getVolume();
596 XSync(mDisplay, False);