/* This file is part of the KDE project Copyright (c) 2004 Jonathan Riddell Based on Demo kmilo service by George Staikos Copyright (c) 2003 George Staikos And tpb by Markus Braun Copyright (C) 2002,2003 Markus Braun FreeBSD support by Markus Brueffer Copyright (C) 2005 Markus Brueffer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "kmilointerface.h" #ifdef Q_OS_FREEBSD #include #endif #include "thinkpad.h" namespace KMilo { ThinkPadMonitor::ThinkPadMonitor(TQObject* parent, const char* name, const TQStringList& args): Monitor(parent, name, args) { m_progress = 0; m_volume = 50; //set in retrieveVolume() } ThinkPadMonitor::~ThinkPadMonitor() { } bool ThinkPadMonitor::init() { KConfig config("kmilodrc"); reconfigure(&config); if (m_run) { clearStruct(thinkpad_state); clearStruct(last_thinkpad_state); if ( getNvramState(&thinkpad_state) == false ) { return false; } if (m_softwareVolume || m_volumeStep != defaultVolumeStep) { kmixClient = new DCOPRef("kmix", "Mixer0"); kmixWindow = new DCOPRef("kmix", "kmix-mainwindow#1"); retrieveVolume(); setNvramVolume(); } } return m_run; } Monitor::DisplayType ThinkPadMonitor::poll() { // save last state and get new one memcpy(&last_thinkpad_state, &thinkpad_state, sizeof(thinkpad_state_struct)); getNvramState(&thinkpad_state); Monitor::DisplayType pollResult = None; // determine the state of the mute button if (thinkpad_state.mute_toggle != last_thinkpad_state.mute_toggle || (thinkpad_state.volume_toggle != last_thinkpad_state.volume_toggle && last_thinkpad_state.mute_toggle == 1)) { showToggleMessage(i18n("Mute on"), i18n("Mute off"), thinkpad_state.mute_toggle == 1); if (m_softwareVolume || m_volumeStep != defaultVolumeStep) { kmixClient->send("setMasterMute", thinkpad_state.mute_toggle == 1); } } // determine the state of the Thinkpad button if (thinkpad_state.thinkpad_toggle != last_thinkpad_state.thinkpad_toggle && thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) { _interface->displayText(i18n("Thinkpad Button Pressed")); KURL url(m_buttonThinkpad); (void) new KRun(url, 0, true, true); } // determine the state of ThinkLight if (thinkpad_state.thinklight_toggle != last_thinkpad_state.thinklight_toggle) { showToggleMessage(i18n("ThinkLight is on"), i18n("ThinkLight is off"), thinkpad_state.thinklight_toggle == 1); } // determine the state of the volume buttons if (thinkpad_state.volume_level != last_thinkpad_state.volume_level) { pollResult = Volume; if (m_volumeStep == defaultVolumeStep && m_softwareVolume == false) { //no need to write to nvram or set volume in software m_progress = thinkpad_state.volume_level * 100 / defaultVolumeStep; } else { if (thinkpad_state.volume_level > last_thinkpad_state.volume_level) { m_progress = m_volume + m_volumeStep; } else { m_progress = m_volume - m_volumeStep; } setVolume(m_progress); } } // determine the state of the brightness buttons if (thinkpad_state.brightness_level != last_thinkpad_state.brightness_level) { pollResult = Brightness; m_progress = thinkpad_state.brightness_level * 100 / 7; } // Buttons below here are untested because they do not exist on my R31 // determine the state of zoom if (thinkpad_state.zoom_toggle != last_thinkpad_state.zoom_toggle) { // showToggleMessage(i18n("Zoom is on"), i18n("Zoom is off"), thinkpad_state.zoom_toggle == 1); // Use as button since an Zooming is implemented _interface->displayText(i18n("Zoom button pressed")); KURL url(m_buttonZoom); (void) new KRun(url, 0, true, true); } // determine the state of the home button if (thinkpad_state.home_toggle != last_thinkpad_state.home_toggle && thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) { _interface->displayText(i18n("Home button pressed")); KURL url(m_buttonHome); (void) new KRun(url, 0, true, true); } // determine the state of the search button if (thinkpad_state.search_toggle != last_thinkpad_state.search_toggle && thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) { _interface->displayText(i18n("Search button pressed")); KURL url(m_buttonSearch); (void) new KRun(url, 0, true, true); } // determine the state of the mail button if (thinkpad_state.mail_toggle != last_thinkpad_state.mail_toggle && thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) { _interface->displayText(i18n("Mail button pressed")); KURL url(m_buttonMail); (void) new KRun(url, 0, true, true); } // determine the state of display if (thinkpad_state.display_toggle != last_thinkpad_state.display_toggle && thinkpad_state.hibernate_toggle == last_thinkpad_state.hibernate_toggle) { // Some thinkpads have no hardware support to switch lcd/crt. They also // don't reflect the current state in thinkpad_state.display_state. So, if // thinkpad_state.display_toggle changes, but thinkpad_state.display_state does // not change, simulate the display state. unsigned int display_state = 1; if (thinkpad_state.display_state == last_thinkpad_state.display_state) { display_state = display_state % 3 + 1; } else { display_state = thinkpad_state.display_state; } switch (display_state & 0x03) { case 0x1: _interface->displayText(i18n("Display changed: LCD on, CRT off")); break; case 0x2: _interface->displayText(i18n("Display changed: LCD off, CRT on")); break; case 0x3: _interface->displayText(i18n("Display changed: LCD on, CRT on")); break; } } // determine the state of hv expansion if (thinkpad_state.expand_toggle != last_thinkpad_state.expand_toggle) { showToggleMessage(i18n("HV Expansion is on"), i18n("HV Expansion is off"), (thinkpad_state.expand_toggle & 0x01) == 1); } // determine power management mode AC if (thinkpad_state.powermgt_ac != last_thinkpad_state.powermgt_ac) { switch(thinkpad_state.powermgt_ac) { case 0x4: _interface->displayText(i18n("Power management mode AC changed: PM AC high")); break; case 0x2: _interface->displayText(i18n("Power management mode AC changed: PM AC auto")); break; case 0x1: _interface->displayText(i18n("Power management mode AC changed: PM AC manual")); break; default: _interface->displayText(i18n("Power management mode AC changed: PM AC unknown")); break; } } // determine power management mode battery if (thinkpad_state.powermgt_battery != last_thinkpad_state.powermgt_battery) { switch(thinkpad_state.powermgt_battery) { case 0x4: _interface->displayText(i18n("Power management mode battery changed: PM battery high")); break; case 0x2: _interface->displayText(i18n("Power management mode battery changed: PM battery auto")); break; case 0x1: _interface->displayText(i18n("Power management mode battery changed: PM battery manual")); break; default: _interface->displayText(i18n("Power management mode battery changed: PM battery unknown")); break; } } // determine the state of wireless lan if (thinkpad_state.wireless_toggle != last_thinkpad_state.wireless_toggle) { showToggleMessage(i18n("Wireless LAN is enabled"), i18n("Wireless LAN is disabled"), thinkpad_state.wireless_toggle == 1); } // determine the state of bluetooth if (thinkpad_state.bluetooth_toggle != last_thinkpad_state.bluetooth_toggle) { showToggleMessage(i18n("Bluetooth is enabled"), i18n("Bluetooth is disabled"), thinkpad_state.bluetooth_toggle == 1); } return pollResult; } int ThinkPadMonitor::progress() const { return m_progress; } TQString ThinkPadMonitor::message() const { //unused //return i18n("yer maw!"); return ""; } bool ThinkPadMonitor::getNvramState(thinkpad_state_struct* thinkpad_state) { #ifndef Q_OS_FREEBSD int file; unsigned char buffer[114]; // open nvram for reading // must use open/close because seek is not supported by nvram if ((file=open(m_nvramFile.latin1(), O_RDONLY|O_NONBLOCK)) == -1) { kdError() << "Unable to open device: " << m_nvramFile << endl; return false; } // read nvram if (read(file, buffer, sizeof(buffer)) != sizeof(buffer)) { kdError() << "Unable to read from device: " << m_nvramFile << endl; return false; } // close nvram device if (close(file) == -1) { kdError() << "Unable to close device %s: " << m_nvramFile << endl; return false; } thinkpad_state->thinkpad_toggle = (thinkpad_state->thinkpad_toggle & ~0x01) | (( buffer[0x57] & 0x08) >> 3); thinkpad_state->zoom_toggle = (thinkpad_state->zoom_toggle & ~0x01) | ((~buffer[0x57] & 0x20) >> 5); thinkpad_state->display_toggle = (thinkpad_state->display_toggle & ~0x01) | (( buffer[0x57] & 0x40) >> 6); thinkpad_state->home_toggle = (thinkpad_state->home_toggle & ~0x01) | (( buffer[0x56] & 0x01) ); thinkpad_state->search_toggle = (thinkpad_state->search_toggle & ~0x01) | (( buffer[0x56] & 0x02) >> 1); thinkpad_state->mail_toggle = (thinkpad_state->mail_toggle & ~0x01) | (( buffer[0x56] & 0x04) >> 2); thinkpad_state->thinklight_toggle = (thinkpad_state->thinklight_toggle & ~0x01) | (( buffer[0x58] & 0x10) >> 4); thinkpad_state->hibernate_toggle = (thinkpad_state->hibernate_toggle & ~0x01) | (( buffer[0x58] & 0x01) ); thinkpad_state->display_state = (( buffer[0x59] & 0x03) ); thinkpad_state->expand_toggle = (thinkpad_state->expand_toggle & ~0x01) | (( buffer[0x59] & 0x10) >> 4); thinkpad_state->brightness_level = (( buffer[0x5E] & 0x07) ); thinkpad_state->brightness_toggle = (thinkpad_state->brightness_toggle & ~0x01) | (( buffer[0x5E] & 0x20) >> 5); thinkpad_state->volume_level = (( buffer[0x60] & 0x0f) ); thinkpad_state->volume_toggle = (thinkpad_state->volume_toggle & ~0x01) | (( buffer[0x60] & 0x80) >> 7); thinkpad_state->mute_toggle = (thinkpad_state->mute_toggle & ~0x01) | (( buffer[0x60] & 0x40) >> 6); thinkpad_state->powermgt_ac = (( buffer[0x39] & 0x07) ); thinkpad_state->powermgt_battery = (( buffer[0x39] & 0x38) >> 3); #else u_int n = 0; size_t len = sizeof(n); if ( sysctlbyname("dev.acpi_ibm.0.hotkey", &n, &len, NULL, 0) == -1 ) { kdError() << "Unable to read sysctl: dev.acpi_ibm.0.hotkey" << endl; return false; } thinkpad_state->thinkpad_toggle = (thinkpad_state->thinkpad_toggle & ~0x01) | (( n & (1<<3)) >> 3); thinkpad_state->zoom_toggle = (thinkpad_state->zoom_toggle & ~0x01) | (( n & (1<<4)) >> 4); thinkpad_state->display_toggle = (thinkpad_state->display_toggle & ~0x01) | (( n & (1<<6)) >> 6); thinkpad_state->home_toggle = (thinkpad_state->home_toggle & ~0x01) | (( n & (1<<0)) ); thinkpad_state->search_toggle = (thinkpad_state->search_toggle & ~0x01) | (( n & (1<<1)) >> 1); thinkpad_state->mail_toggle = (thinkpad_state->mail_toggle & ~0x01) | (( n & (1<<2)) >> 2); thinkpad_state->hibernate_toggle = (thinkpad_state->hibernate_toggle & ~0x01) | (( n & (1<<7)) >> 7); thinkpad_state->expand_toggle = (thinkpad_state->expand_toggle & ~0x01) | (( n & (1<<9)) >> 9); thinkpad_state->brightness_toggle = (thinkpad_state->brightness_toggle & ~0x01) | (( n & (1<<10)) >> 10); thinkpad_state->volume_toggle = (thinkpad_state->volume_toggle & ~0x01) | (( n & (1<<11)) >> 11); // Don't fail if the thinklight sysctl is not present. It is generated dynamically if ( sysctlbyname("dev.acpi_ibm.0.thinklight", &n, &len, NULL, 0) != -1 ) thinkpad_state->thinklight_toggle = n; else kdWarning() << "Unable to read sysctl: dev.acpi_ibm.0.thinklight" << endl; if ( sysctlbyname("dev.acpi_ibm.0.lcd_brightness", &n, &len, NULL, 0) == -1 ) { kdError() << "Unable to read sysctl: dev.acpi_ibm.0.lcd_brightness" << endl; return false; } thinkpad_state->brightness_level = n; if ( sysctlbyname("dev.acpi_ibm.0.volume", &n, &len, NULL, 0) == -1 ) { kdError() << "Unable to read sysctl: dev.acpi_ibm.0.volume" << endl; return false; } thinkpad_state->volume_level = n; if ( sysctlbyname("dev.acpi_ibm.0.mute", &n, &len, NULL, 0) == -1 ) { kdError() << "Unable to read sysctl: dev.acpi_ibm.0.mute" << endl; return false; } thinkpad_state->mute_toggle = n; // Don't fail if wlan or bluetooth sysctls are not present. They are generated dynamically if ( sysctlbyname("dev.acpi_ibm.0.wlan", &n, &len, NULL, 0) != -1 ) thinkpad_state->wireless_toggle = n; else kdWarning() << "Unable to read sysctl: dev.acpi_ibm.0.wlan" << endl; if ( sysctlbyname("dev.acpi_ibm.0.bluetooth", &n, &len, NULL, 0) != -1 ) thinkpad_state->bluetooth_toggle = n; else kdWarning() << "Unable to read sysctl: dev.acpi_ibm.0.bluetooth" << endl; #endif return true; } void ThinkPadMonitor::clearStruct(thinkpad_state_struct& thinkpad_state) { thinkpad_state.thinkpad_toggle = 0; thinkpad_state.zoom_toggle = 0; thinkpad_state.display_toggle = 0; thinkpad_state.home_toggle = 0; thinkpad_state.search_toggle = 0; thinkpad_state.mail_toggle = 0; thinkpad_state.favorites_toggle = 0; thinkpad_state.reload_toggle = 0; thinkpad_state.abort_toggle = 0; thinkpad_state.backward_toggle = 0; thinkpad_state.forward_toggle = 0; thinkpad_state.fn_toggle = 0; thinkpad_state.thinklight_toggle = 0; thinkpad_state.hibernate_toggle = 0; thinkpad_state.display_state = 0; thinkpad_state.expand_toggle = 0; thinkpad_state.brightness_level = 0; thinkpad_state.brightness_toggle = 0; thinkpad_state.volume_level = 0; thinkpad_state.volume_toggle = 0; thinkpad_state.mute_toggle = 0; thinkpad_state.ac_state = 0; thinkpad_state.powermgt_ac = 0; thinkpad_state.powermgt_battery = 0; thinkpad_state.wireless_toggle = 0; thinkpad_state.bluetooth_toggle = 0; } void ThinkPadMonitor::showToggleMessage(TQString onMessage, TQString offMessage, bool state) { TQString message; if (state) { message = onMessage; } else { message = offMessage; } _interface->displayText(message); } void ThinkPadMonitor::reconfigure(KConfig* config) { config->setGroup("thinkpad"); m_nvramFile = config->readEntry("nvram", "/dev/nvram"); m_softwareVolume = config->readBoolEntry("softwareVolume", true); m_run = config->readBoolEntry("run", false); m_volumeStep = config->readNumEntry("volumeStep", defaultVolumeStep); m_buttonThinkpad = config->readEntry("buttonThinkpad", "/usr/bin/konsole"); m_buttonHome = config->readEntry("buttonHome", "/usr/bin/konqueror"); m_buttonSearch = config->readEntry("buttonSearch", "/usr/bin/kfind"); m_buttonMail = config->readEntry("buttonMail", "/usr/bin/kmail"); m_buttonZoom = config->readEntry("buttonZoom", "/usr/bin/ksnapshot"); } bool ThinkPadMonitor::retrieveVolume() { bool kmix_error = false; DCOPReply reply = kmixClient->call("masterVolume"); if (reply.isValid()) { m_volume = reply; } else { kmix_error = true; } if (kmix_error) { // maybe the error occurred because kmix wasn't running if (kapp->startServiceByDesktopName("kmix")==0) { // trying to start kmix // trying again reply = kmixClient->call("masterVolume"); if (reply.isValid()) { m_volume = reply; kmix_error = false; kmixWindow->send("hide"); } } } if (kmix_error) { kdError() << "KMilo: ThinkPadMonitor could not access kmix/Mixer0 via dcop" << endl; return false; } else { return true; } } void ThinkPadMonitor::setVolume(int volume) { if (!retrieveVolume()) { return; } if (volume > 100) { m_volume = 100; } else if (volume < 0) { m_volume = 0; } else { m_volume = volume; } kmixClient->send("setMasterVolume", m_volume); //write back to nvram but only if volume steps is not default if (m_volumeStep != defaultVolumeStep) { setNvramVolume(); } m_progress = m_volume; } void ThinkPadMonitor::setNvramVolume() { #ifndef Q_OS_FREEBSD int file; char buffer; //open nvram if ((file = open(m_nvramFile.latin1(), O_RDWR|O_NONBLOCK)) == -1) { kdError() << "Unable to open device " << m_nvramFile << endl; return; } // jump to volume section if (lseek(file, 0x60, SEEK_SET) == -1 ) { kdError() << "Unable to seek device " << m_nvramFile << endl; return; } // read nvram if (read(file, &buffer, sizeof(buffer)) != sizeof(buffer)) { kdError() << "Unable to read from device " << m_nvramFile << endl; return; } // set volume_level to the value we write back to nvram // taken from tpb, unfortunatly I'm not sure why it works thinkpad_state.volume_level = 0x07; buffer &= 0xf0; buffer |= thinkpad_state.volume_level; // jump to volume section if (lseek(file, 0x60, SEEK_SET) == -1 ) { kdError() << "Unable to seek device " << m_nvramFile << endl; return; } // write std value for volume if (write(file, &buffer, sizeof(buffer)) != sizeof(buffer)) { kdError() << "Unable to write to device " << m_nvramFile << endl; return; } close(file); #else u_int n = thinkpad_state.volume_level; if (sysctlbyname("dev.acpi_ibm.0.volume", NULL, NULL, &n, sizeof(n))) kdError() << "Unable to write sysctl: dev.acpi_ibm.0.volume" << endl; #endif } } K_EXPORT_COMPONENT_FACTORY(kmilo_thinkpad, KGenericFactory("kmilo_thinkpad"))