wmswallow: Add version 0.6.1 to repository.
[dockapps.git] / AlsaMixer.app / Mixer.cc
blob66ebe6eb3f205c93b6e60dd44380ee13619b834f
1 //
2 // Mixer.app
3 //
4 // Copyright (c) 1998-2002 Per Liden
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1307,
19 // USA.
22 #include <X11/Xlib.h>
23 #include <iostream>
24 #include <fstream>
25 #include <cstdlib>
26 #include <cstring>
27 #include <csignal>
28 #include "Xpm.h"
29 #include "Mixer.h"
31 #include "AMixer/AMixer.h"
33 #include "pixmaps/main.xpm"
34 #include "pixmaps/button.xpm"
35 #include "pixmaps/mutebutton.xpm"
36 #include "pixmaps/redlight.xpm"
38 #define ROUND_POS(x) (int ((x) + 0.5) > int (x)) ? (int) ((x) + 1) : (int) (x)
40 using namespace std;
42 static const int ButtonX[] = {6, 24, 42};
43 static const char* MixerSources[] = { "Master", "PCM", "CD" };
45 extern Mixer* app;
47 void catchBrokenPipe(int sig)
49 app->saveVolumeSettings();
50 exit(0);
53 int positionToPercent(int position) {
54 return ROUND_POS(100 - (((position - BUTTON_MAX) * 100.0) / (BUTTON_MIN - BUTTON_MAX)));
57 int percentToPosition(int percent) {
58 return ROUND_POS(BUTTON_MIN - (percent * (BUTTON_MIN - BUTTON_MAX)) / 100.0);
61 Mixer::Mixer(int argc, char** argv)
63 XClassHint classHint;
64 XSizeHints sizeHints;
65 XWMHints wmHints;
66 Atom deleteWindow;
67 Xpm* image;
68 char* displayName = NULL;
69 char* card = "default";
71 mError = 0;
72 mInstanceName = INSTANCENAME;
73 mVolumeSource[0] = -1;
74 mVolumeSource[1] = -1;
75 mVolumeSource[2] = -1;
76 mVolumeMute[0] = 0;
77 mVolumeMute[1] = 0;
78 mVolumeMute[2] = 0;
79 mWheelButton = 1;
80 mLabelText = 0;
81 mSettingsFile = 0;
82 mSaveSettings = false;
83 mLoadSettings = false;
84 mCommand = NULL;
86 // Parse command line
87 if (argc>1) {
88 for (int i=1; i<argc; i++) {
89 // Display
90 if (!strcmp(argv[i], "-d")) {
91 checkArgument(argv, argc, i);
92 displayName = argv[i+1];
93 i++;
96 // Sound source
97 else if (!strcmp(argv[i], "-1") || !strcmp(argv[i], "-2") || !strcmp(argv[i], "-3")) {
98 checkArgument(argv, argc, i);
99 MixerSources[argv[i][1] - '1'] = argv[i + 1];
100 i++;
103 // Wheel binding
104 else if (!strcmp(argv[i], "-w")) {
105 checkArgument(argv, argc, i);
106 mWheelButton = atoi(argv[i+1]);
108 if (mWheelButton < 1 || mWheelButton > 3) {
109 cerr << APPNAME << ": invalid wheel binding, must be 1, 2 or 3, not " << argv[i+1] << endl;
110 tryHelp(argv[0]);
111 exit(0);
114 i++;
117 // Label text
118 else if (!strcmp(argv[i], "-l")) {
119 checkArgument(argv, argc, i);
120 mLabelText = argv[i+1];
121 i++;
124 // Save settings on exit
125 else if (!strcmp(argv[i], "-S")) {
126 mSaveSettings = true;
129 // Load settings on startup
130 else if (!strcmp(argv[i], "-L")) {
131 mLoadSettings = true;
134 // Load/Save settings file
135 else if (!strcmp(argv[i], "-f")) {
136 checkArgument(argv, argc, i);
137 mSettingsFile = argv[i+1];
138 i++;
141 // Execute command on middle click
142 else if (!strcmp(argv[i], "-e")) {
143 checkArgument(argv, argc, i);
144 mCommand = argv[i + 1];
145 i++;
148 // Instance name
149 else if (!strcmp(argv[i], "-n")) {
150 checkArgument(argv, argc, i);
151 mInstanceName = argv[i+1];
152 i++;
155 // Version
156 else if (!strcmp(argv[i], "-v")) {
157 cerr << APPNAME << " version " << VERSION << endl;
158 exit(0);
161 // Help
162 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
163 showHelp();
164 exit(0);
167 // card
168 else if (!strcmp(argv[i], "--card")) {
169 card = AMixer::convertIDToCard(argv[i + 1]);
170 if (!card) {
171 cerr << APPNAME << ": invalid card number '" << argv[i + 1] << "'" << endl;
172 tryHelp(argv[0]);
173 exit(0);
175 i++;
178 // device
179 else if (!strcmp(argv[i], "--device")) {
180 card = argv[i + 1];
181 i++;
184 // Unknown option
185 else {
186 cerr << APPNAME << ": invalid option '" << argv[i] << "'" << endl;
187 tryHelp(argv[0]);
188 exit(0);
193 // default settings file
194 if (!mSettingsFile) {
195 char* home = getenv("HOME");
196 if (home) {
197 mSettingsFile = new char[strlen(home) + strlen(SETTINGS) + 1];
198 strcpy(mSettingsFile, home);
199 strcat(mSettingsFile, SETTINGS);
200 } else {
201 cerr << APPNAME << ": $HOME not set, could not find saved settings" << endl;
205 // init mixer
206 aMixer = new AMixer(card);
207 if (!aMixer->opened()) {
208 cerr << APPNAME << ": could not open mixer device for card '" << card << "'" << endl;
209 exit(0);
212 // open mixer sources
213 for (int i = 0; i < 3; i++) {
214 aMixer->attachItem(i, MixerSources[i]);
215 if (!aMixer->itemOK(i))
216 cerr << APPNAME << ": could not select mixer source '" << MixerSources[i] << "'" << endl;
219 // Open display
220 if ((mDisplay = XOpenDisplay(displayName)) == NULL) {
221 cerr << APPNAME << ": could not open display " << displayName << endl;
222 exit(0);
225 // Get root window
226 mRoot = RootWindow(mDisplay, DefaultScreen(mDisplay));
228 // Create windows
229 mAppWin = XCreateSimpleWindow(mDisplay, mRoot, 1, 1, 64, 64, 0, 0, 0);
230 mIconWin = XCreateSimpleWindow(mDisplay, mAppWin, 0, 0, 64, 64, 0, 0, 0);
232 // Set classhint
233 classHint.res_name = mInstanceName;
234 classHint.res_class = CLASSNAME;
235 XSetClassHint(mDisplay, mAppWin, &classHint);
237 // Create delete atom
238 deleteWindow = XInternAtom(mDisplay, "WM_DELETE_WINDOW", False);
239 XSetWMProtocols(mDisplay, mAppWin, &deleteWindow, 1);
240 XSetWMProtocols(mDisplay, mIconWin, &deleteWindow, 1);
242 // Set windowname
243 XStoreName(mDisplay, mAppWin, APPNAME);
244 XSetIconName(mDisplay, mAppWin, APPNAME);
246 // Set sizehints
247 sizeHints.flags= USPosition;
248 sizeHints.x = 0;
249 sizeHints.y = 0;
250 XSetWMNormalHints(mDisplay, mAppWin, &sizeHints);
252 // Set wmhints
253 wmHints.initial_state = WithdrawnState;
254 wmHints.icon_window = mIconWin;
255 wmHints.icon_x = 0;
256 wmHints.icon_y = 0;
257 wmHints.window_group = mAppWin;
258 wmHints.flags = StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
259 XSetWMHints(mDisplay, mAppWin, &wmHints);
261 // Set command
262 XSetCommand(mDisplay, mAppWin, argv, argc);
264 // Set background image
265 image = new Xpm(mDisplay, mRoot, main_xpm);
266 if (mLabelText) {
267 image->drawString(LABEL_X, LABEL_Y, mLabelText);
269 image->setWindowPixmapShaped(mIconWin);
270 delete image;
272 // Create buttons
273 mButton[0] = XCreateSimpleWindow(mDisplay, mIconWin, ButtonX[0], BUTTON_MIN, 5, 5, 0, 0, 0);
274 mButton[1] = XCreateSimpleWindow(mDisplay, mIconWin, ButtonX[1], BUTTON_MIN, 5, 5, 0, 0, 0);
275 mButton[2] = XCreateSimpleWindow(mDisplay, mIconWin, ButtonX[2], BUTTON_MIN, 5, 5, 0, 0, 0);
277 image = new Xpm(mDisplay, mRoot, button_xpm);
278 image->setWindowPixmap(mButton[0]);
279 image->setWindowPixmap(mButton[1]);
280 image->setWindowPixmap(mButton[2]);
281 delete image;
283 XSelectInput(mDisplay, mButton[0], ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
284 XSelectInput(mDisplay, mButton[1], ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
285 XSelectInput(mDisplay, mButton[2], ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
286 XSelectInput(mDisplay, mIconWin, ButtonPressMask);
288 XMapWindow(mDisplay, mButton[0]);
289 XMapWindow(mDisplay, mButton[1]);
290 XMapWindow(mDisplay, mButton[2]);
292 XMapWindow(mDisplay, mIconWin);
293 XMapWindow(mDisplay, mAppWin);
294 XSync(mDisplay, False);
296 // Catch broker pipe signal
297 signal(SIGPIPE, catchBrokenPipe);
299 // Check if error
300 if (mError) {
301 showErrorLed();
302 } else {
303 getVolume();
304 if (mLoadSettings)
305 loadVolumeSettings();
309 void Mixer::tryHelp(char* appname)
311 cerr << "Try `" << appname << " --help' for more information" << endl;
314 void Mixer::showHelp()
316 cerr << APPNAME << " Copyright (c) 1998-2002 by Per Liden (per@fukt.bth.se), Petr Hlavka (xhlavk00@stud.fit.vutbr.cz)" << endl << endl
317 << "options:" << endl
318 << " -1 <source> set sound source for control 1 (default is Master)" << endl
319 << " -2 <source> set sound source for control 2 (default is PCM)" << endl
320 << " -3 <source> set sound source for control 3 (default is CD)" << endl
321 << " -w 1|2|3 bind a control button to the mouse wheel (default is 1)" << endl
322 << " -l <text> set label text" << endl
323 << " -S save volume settings on exit" << endl
324 << " -L load volume settings on start up" << endl
325 << " -f <file> use setting <file> instead of ~/GNUstep/Defaults/AlsaMixer" << endl
326 << " --card <id> select card" << endl
327 << " --device <dev> select device, default 'default'" << endl
328 << " -e <command> execute <command> on middle click" << endl
329 << " -n <name> set client instance name" << endl
330 << " -d <disp> set display" << endl
331 << " -v print version and exit" << endl
332 << " -h, --help display this help and exit" << endl << endl;
335 void Mixer::checkArgument(char** argv, int argc, int index)
337 if (argc-1 < index+1) {
338 cerr << APPNAME << ": option '" << argv[index] << "' requires an argument" << endl;
339 tryHelp(argv[0]);
340 exit(0);
344 void Mixer::showErrorLed()
346 Window led;
347 Xpm* image;
349 led = XCreateSimpleWindow(mDisplay, mIconWin, LED_X, LED_Y, 3, 2, 0, 0, 0);
351 // Set background image
352 image = new Xpm(mDisplay, mRoot, redlight_xpm);
353 image->setWindowPixmap(led);
354 delete image;
356 // Show window
357 XMapWindow(mDisplay, led);
358 mError = 1;
361 void Mixer::loadVolumeSettings()
363 if (mSettingsFile) {
364 ifstream file(mSettingsFile);
365 if (file) {
366 // This could fail if the user has edited the file by hand and destroyed the structure
367 char dummy[1024];
368 file >> dummy; // {
369 file >> dummy; // Volume1
370 file >> dummy; // =
371 file >> mVolume[0];
372 file >> dummy; // ;
374 file >> dummy; // Volume2
375 file >> dummy; // =
376 file >> mVolume[1];
377 file >> dummy; // ;
379 file >> dummy; // Volume3
380 file >> dummy; // =
381 file >> mVolume[2];
383 file.close();
384 for (int i = 0; i < 3; i++) {
385 setVolume(i, mVolume[i]);
386 setButtonPosition(i, percentToPosition(mVolume[i]));
392 void Mixer::saveVolumeSettings()
394 if (mSaveSettings) {
395 ofstream file(mSettingsFile);
396 if (file) {
397 // Files in ~/GNUstep/Defaults/ should follow the property list format
398 file << "{" << endl
399 << " Volume1 = " << mVolumePos[0] << ";" << endl
400 << " Volume2 = " << mVolumePos[1] << ";" << endl
401 << " Volume3 = " << mVolumePos[2] << ";" << endl
402 << "}" << endl;
403 file.close();
404 } else {
405 cerr << APPNAME << ": failed to save volume settings in " << mSettingsFile << endl;
410 void Mixer::getVolume()
412 static int lastVolume[3] = {-1, -1, -1};
413 static int lastVolumeMute[3] = {-1, -1, -1};
415 if (mError) {
416 return;
419 // Read from device
420 for (int i=0; i<3; i++) {
421 mVolume[i] = aMixer->itemGetVolume(i);
422 mVolumeMute[i] = aMixer->itemIsMuted(i);
424 if (lastVolume[i] != mVolume[i]) {
425 int y;
427 // Set button position
428 if (mError) {
429 y = BUTTON_MIN;
430 } else {
431 y = percentToPosition(mVolume[i]);
434 setButtonPosition(i, y);
435 lastVolume[i] = mVolume[i];
438 // set buttom type muted/unmuted
439 if (lastVolumeMute[i] != mVolumeMute[i]) {
440 setButtonType(i);
441 lastVolumeMute[i] = mVolumeMute[i];
445 if (mError) {
446 cerr << APPNAME << ": unable to read from " << mMixerDevice << endl;
447 showErrorLed();
448 return;
452 void Mixer::setVolume(int button, int volume)
454 if (mError) {
455 return;
458 // Store volume
459 mVolume[button] = volume;
461 // Write to device
462 aMixer->itemSetVolume(button, mVolume[button]);
465 void Mixer::toggleMute(int button)
467 aMixer->itemToggleMute(button);
468 mVolumeMute[button] = aMixer->itemIsMuted(button);
469 setButtonType(button);
472 void Mixer::setButtonType(int button)
474 Xpm* image;
476 if (mVolumeMute[button] == 1) { // muted
477 image = new Xpm(mDisplay, mRoot, mutebutton_xpm);
478 image->setWindowPixmap(mButton[button]);
479 delete image;
481 XClearWindow(mDisplay, mButton[button]);
482 } else {
483 image = new Xpm(mDisplay, mRoot, button_xpm);
484 image->setWindowPixmap(mButton[button]);
485 delete image;
487 XClearWindow(mDisplay, mButton[button]);
491 void Mixer::setButtonPosition(int button, int position) {
492 if (position > BUTTON_MIN) {
493 position = BUTTON_MIN;
494 } else if (position < BUTTON_MAX) {
495 position = BUTTON_MAX;
498 XMoveWindow(mDisplay, mButton[button], ButtonX[button], position);
500 mVolumePos[button] = position;
503 void Mixer::setButtonPositionRelative(int button, int relativePosition)
505 int y;
507 // Calc new button position
508 y = mVolumePos[button] + relativePosition;
510 if (y > BUTTON_MIN) {
511 y = BUTTON_MIN;
512 } else if (y < BUTTON_MAX) {
513 y = BUTTON_MAX;
516 // Set button position and volume
517 XMoveWindow(mDisplay, mButton[button], ButtonX[button], y);
519 mVolumePos[button] = y;
521 // set volume
522 setVolume(button, positionToPercent(y));
525 void Mixer::run()
527 XEvent event;
528 int buttonDown = 0;
529 int buttonDownPosition = 0;
531 // Start handling events
532 while(1) {
533 while(XPending(mDisplay) || buttonDown) {
534 XNextEvent(mDisplay, &event);
536 switch(event.type) {
537 case ButtonPress:
538 if (event.xbutton.button == Button4 || event.xbutton.button == Button5) {
539 // Wheel scroll
540 setButtonPositionRelative(mWheelButton - 1, event.xbutton.button == Button5? 3: -3);
541 } else if (event.xbutton.button == Button1 && event.xbutton.window != mIconWin) {
542 // Volume change
543 buttonDown = 1;
544 buttonDownPosition = event.xbutton.y;
545 } else if (event.xbutton.button == Button3 && buttonDown == 0 && event.xbutton.window != mIconWin) {
546 // Mute
547 for (int i=0; i<3; i++) {
548 if (mButton[i] == event.xbutton.window) {
549 toggleMute(i);
550 break;
553 } else if (event.xbutton.button == Button2) {
554 // Load defaults or execute command
555 if (mCommand) {
556 char command[512];
558 snprintf(command, 512, "%s &", mCommand);
559 system(command);
561 else
562 loadVolumeSettings();
564 break;
566 case ButtonRelease:
567 if (event.xbutton.button == Button1) {
568 buttonDown = 0;
570 break;
572 case MotionNotify:
573 if (buttonDown) {
574 // Find button
575 for (int i=0; i<3; i++) {
576 if (mButton[i] == event.xmotion.window) {
577 setButtonPositionRelative(i, event.xmotion.y - buttonDownPosition);
578 break;
582 break;
586 // Idle for a moment
587 usleep(100000);
589 // Update volume status
590 aMixer->handleEvents();
591 if (AMixer::mixerChanged())
592 aMixer->reInit();
593 else if (AMixer::mixerElemsChanged())
594 getVolume();
595 XSync(mDisplay, False);