From ac2ae16f47e0fb54ab3570ef07abc7cef00f44cb Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 3 Jun 2015 21:13:01 +0300 Subject: [PATCH] MSVC instruction improved for OpenAL, merging autoupdate code for all OSs (not tested yet!) --- MSVC.md | 18 +- Telegram/SourceFiles/_other/updater.cpp | 42 +- Telegram/SourceFiles/_other/updater_linux.cpp | 38 +- Telegram/SourceFiles/_other/updater_osx.m | 21 +- Telegram/SourceFiles/application.cpp | 30 +- Telegram/SourceFiles/application.h | 12 +- Telegram/SourceFiles/autoupdater.cpp | 520 +++ Telegram/SourceFiles/autoupdater.h | 62 + Telegram/SourceFiles/pspecific_linux.cpp | 581 +-- Telegram/SourceFiles/pspecific_linux.h | 51 +- Telegram/SourceFiles/pspecific_mac.cpp | 477 +-- Telegram/SourceFiles/pspecific_mac.h | 49 +- Telegram/SourceFiles/pspecific_wnd.cpp | 422 +- Telegram/SourceFiles/pspecific_wnd.h | 49 +- Telegram/SourceFiles/settingswidget.cpp | 4 +- Telegram/SourceFiles/sysbuttons.cpp | 3 +- Telegram/Telegram.vcxproj | 27 + Telegram/Telegram.vcxproj.filters | 15 + Telegram/_openal_patch.diff | 35 + Telegram/_openal_patch/Alc/ALc.c | 3771 ----------------- .../_openal_patch/Alc/backends/mmdevapi.c | 1789 ++++++++ Telegram/_openal_patch/Alc/backends/winmm.c | 670 +-- 22 files changed, 2994 insertions(+), 5692 deletions(-) create mode 100644 Telegram/SourceFiles/autoupdater.cpp create mode 100644 Telegram/SourceFiles/autoupdater.h create mode 100644 Telegram/_openal_patch.diff delete mode 100644 Telegram/_openal_patch/Alc/ALc.c create mode 100644 Telegram/_openal_patch/Alc/backends/mmdevapi.c diff --git a/MSVC.md b/MSVC.md index 303a4122c7..c93ac3d237 100644 --- a/MSVC.md +++ b/MSVC.md @@ -66,13 +66,23 @@ or download in ZIP and extract to **D:\TBuild\Libraries\**, rename **libexif-0.6 * Build Debug configuration * Build Release configuration -####OpenAL Soft +####OpenAL Soft, slightly patched Get sources by git – in [Git Bash](http://git-scm.com/downloads) go to **/d/tbuild/libraries** and run git clone git://repo.or.cz/openal-soft.git -to have **D:\TBuild\Libraries\openal-soft\CMakeLists.txt** +to have **D:\TBuild\Libraries\openal-soft\CMakeLists.txt**, then in [Git Bash](http://git-scm.com/downloads) go to **/d/tbuild/libraries/openal-soft** and run + + git checkout 9479ea656b + +Apply patch + +* OR copy (with overwrite!) everything from **D:\\TBuild\\tdesktop\\\_openal\_patch\\** to **D:\\TBuild\\Libraries\\openal-soft\\** +* OR in Git Bash go to **/d/tbuild/libraries/openal-soft/** and run + + git apply ./../../tdesktop/Telegram/_openal_patch.diff + #####Building library @@ -138,9 +148,9 @@ Extract to **D:\TBuild\Libraries\**, rename **qt-everywhere-opensource-src-5.4.0 Apply patch * OR copy (with overwrite!) everything from **D:\TBuild\tdesktop\\\_qt\_5\_4\_0\_patch\** to **D:\TBuild\Libraries\QtStatic\** -* OR copy **D:\TBuild\tdesktop\\\_qt\_5\_4\_0\_patch.diff** to **D:\TBuild\Libraries\QtStatic\**, go there in Git Bash and run +* OR in Git Bash go to **/d/TBuild/Libraries/QtStatic/** and run - git apply _qt_5_4_0_patch.diff + git apply ./../../tdesktop/Telegram/_qt_5_4_0_patch.diff #####Building library diff --git a/Telegram/SourceFiles/_other/updater.cpp b/Telegram/SourceFiles/_other/updater.cpp index c4bcb97e75..b9b7a25878 100644 --- a/Telegram/SourceFiles/_other/updater.cpp +++ b/Telegram/SourceFiles/_other/updater.cpp @@ -96,12 +96,11 @@ void writeLog(const wstring &msg) { } } -void delFolder() { - wstring delPath = L"tupdates\\ready", delFolder = L"tupdates"; +void fullClearPath(const wstring &dir) { WCHAR path[4096]; - memcpy(path, delPath.c_str(), (delPath.size() + 1) * sizeof(WCHAR)); - path[delPath.size() + 1] = 0; - writeLog(L"Fully clearing path '" + delPath + L"'.."); + memcpy(path, dir.c_str(), (dir.size() + 1) * sizeof(WCHAR)); + path[dir.size() + 1] = 0; + writeLog(L"Fully clearing path '" + dir + L"'.."); SHFILEOPSTRUCT file_op = { NULL, FO_DELETE, @@ -116,13 +115,27 @@ void delFolder() { }; int res = SHFileOperation(&file_op); if (res) writeLog(L"Error: failed to clear path! :("); +} + +void delFolder() { + wstring delPathOld = L"tupdates\\ready", delPath = L"tupdates\\temp", delFolder = L"tupdates"; + fullClearPath(delPathOld); + fullClearPath(delPath); RemoveDirectory(delFolder.c_str()); } bool update() { writeLog(L"Update started.."); - wstring updDir = L"tupdates\\ready"; + wstring updDir = L"tupdates\\temp", readyFilePath = L"tupdates\\temp\\ready"; + { + HANDLE readyFile = CreateFile(readyFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (readyFile != INVALID_HANDLE_VALUE) { + CloseHandle(readyFile); + } else { + updDir = L"tupdates\\ready"; // old + } + } deque dirs; dirs.push_back(updDir); @@ -166,10 +179,13 @@ bool update() { writeLog(L"Error: bad update, has Updater.exe! '" + tofname + L"' equal '" + exeName + L"'"); delFolder(); return false; + } else if (equal(fname, readyFilePath)) { + writeLog(L"Skipped ready file '" + fname + L"'"); + } else { + from.push_back(fname); + to.push_back(tofname); + writeLog(L"Added file '" + fname + L"' to be copied to '" + tofname + L"'"); } - from.push_back(fname); - to.push_back(tofname); - writeLog(L"Added file '" + fname + L"' to be copied to '" + tofname + L"'"); } } while (FindNextFile(findHandle, &findData)); DWORD errorCode = GetLastError(); @@ -357,7 +373,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParama updateRegistry(); } if (writeprotected) { // if we can't clear all tupdates\ready (Updater.exe is there) - clear only version - if (DeleteFile(L"tupdates\\ready\\tdata\\version")) { + if (DeleteFile(L"tupdates\\temp\\tdata\\version") || DeleteFile(L"tupdates\\ready\\tdata\\version")) { writeLog(L"Version file deleted!"); } else { writeLog(L"Error: could not delete version file"); @@ -386,7 +402,6 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParama HRESULT hres = CoInitialize(0); if (SUCCEEDED(hres)) { - wstring lnk = L"tupdates\\ready\\temp.lnk"; IShellLink* psl; HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); if (SUCCEEDED(hres)) { @@ -401,7 +416,12 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParama hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); if (SUCCEEDED(hres)) { + wstring lnk = L"tupdates\\temp\\temp.lnk"; hres = ppf->Save(lnk.c_str(), TRUE); + if (!SUCCEEDED(hres)) { + lnk = L"tupdates\\ready\\temp.lnk"; // old + hres = ppf->Save(lnk.c_str(), TRUE); + } ppf->Release(); if (SUCCEEDED(hres)) { diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index 09ab4c676e..d53aa6b7f1 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -136,7 +136,7 @@ bool remove_directory(const string &path) { // from http://stackoverflow.com/que if (!d) { writeLog("Could not open dir '%s'", path.c_str()); - return false; + return (errno == ENOENT); } while (struct dirent *p = readdir(d)) { @@ -201,18 +201,30 @@ bool equal(string a, string b) { } void delFolder() { - string delPath = workDir + "tupdates/ready", delFolder = workDir + "tupdates"; - writeLog("Fully clearing path '%s'..", delPath.c_str()); - if (!remove_directory(delPath)) { - writeLog("Error: failed to clear path! :("); + string delPathOld = workDir + "tupdates/ready", delPath = workDir + "tupdates/temp", delFolder = workDir + "tupdates"; + writeLog("Fully clearing old path '%s'..", delPathOld.c_str()); + if (!remove_directory(delPathOld)) { + writeLog("Error: failed to clear old path! :("); } - rmdir(delFolder.c_str()); + writeLog("Fully clearing path '%s'..", delPath.c_str()); + if (!remove_directory(delPath)) { + writeLog("Error: failed to clear path! :("); + } + rmdir(delFolder.c_str()); } bool update() { writeLog("Update started.."); - string updDir = workDir + "tupdates/ready"; + string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready"; + { + FILE *readyFile = fopen(readyFilePath.c_str(), "rb"); + if (readyFile) { + fclose(readyFile); + } else { + updDir = workDir + "tupdates/ready"; // old + } + } deque dirs; dirs.push_back(updDir); @@ -253,9 +265,13 @@ bool update() { delFolder(); return false; } - from.push_back(fname); - to.push_back(tofname); - writeLog("Added file '%s' to be copied to '%s'", fname.c_str(), tofname.c_str()); + if (fname == readyFilePath) { + writeLog("Skipped ready file '%s'", fname.c_str()); + } else { + from.push_back(fname); + to.push_back(tofname); + writeLog("Added file '%s' to be copied to '%s'", fname.c_str(), tofname.c_str()); + } } } else { writeLog("Could not get stat() for file %s", fname.c_str()); @@ -336,7 +352,7 @@ int main(int argc, char *argv[]) { exeDir = exeName.substr(0, exeName.size() - 7); writeLog("Exe dir is: %s", exeDir.c_str()); if (needupdate) { - if (workDir.empty()) { // old app launched + if (workDir.empty()) { // old app launched, update prepared in tupdates/ready (not in tupdates/temp) writeLog("No workdir, trying to figure it out"); struct passwd *pw = getpwuid(getuid()); if (pw && pw->pw_dir && strlen(pw->pw_dir)) { diff --git a/Telegram/SourceFiles/_other/updater_osx.m b/Telegram/SourceFiles/_other/updater_osx.m index 2839a037ba..1d0a1d9a77 100644 --- a/Telegram/SourceFiles/_other/updater_osx.m +++ b/Telegram/SourceFiles/_other/updater_osx.m @@ -55,7 +55,8 @@ void writeLog(NSString *msg) { } void delFolder() { - [[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/ready"] error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/ready"] error:nil]; // remove old + [[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/temp"] error:nil]; rmdir([[workDir stringByAppendingString:@"tupdates"] fileSystemRepresentation]); } @@ -132,13 +133,19 @@ int main(int argc, const char * argv[]) { } if (update) { - writeLog([@"Starting update files iteration, path: " stringByAppendingString: [workDir stringByAppendingString:@"tupdates/ready"]]); - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *srcDir = [workDir stringByAppendingString:@"tupdates/ready/"]; + NSString *readyFilePath = [workDir stringByAppendingString:@"tupdates/temp/ready"]; + NSString *srcDir = [workDir stringByAppendingString:@"tupdates/temp/"], *srcEnum = [workDir stringByAppendingString:@"tupdates/temp"]; + if (![fileManager fileExistsAtPath:readyFilePath]) { + srcDir = [workDir stringByAppendingString:@"tupdates/ready/"]; // old + srcEnum = [workDir stringByAppendingString:@"tupdates/ready"]; + } + + writeLog([@"Starting update files iteration, path: " stringByAppendingString: srcEnum]); + NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey]; NSDirectoryEnumerator *enumerator = [fileManager - enumeratorAtURL:[NSURL fileURLWithPath:[workDir stringByAppendingString:@"tupdates/ready"]] + enumeratorAtURL:[NSURL fileURLWithPath:srcEnum] includingPropertiesForKeys:keys options:0 errorHandler:^(NSURL *url, NSError *error) { @@ -163,18 +170,20 @@ int main(int argc, const char * argv[]) { NSString *dstPath = [appDirFull stringByAppendingString:[pathPart substringFromIndex:r.length]]; NSError *error; NSNumber *isDirectory = nil; - writeLog([[NSArray arrayWithObjects: @"Copying file ", srcPath, @" to ", dstPath, nil] componentsJoinedByString:@""]); if (![url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) { writeLog([@"Failed to get IsDirectory for file " stringByAppendingString:[url path]]); delFolder(); break; } if ([isDirectory boolValue]) { + writeLog([[NSArray arrayWithObjects: @"Copying dir ", srcPath, @" to ", dstPath, nil] componentsJoinedByString:@""]); if (![fileManager createDirectoryAtPath:dstPath withIntermediateDirectories:YES attributes:nil error:nil]) { writeLog([@"Failed to force path for directory " stringByAppendingString:dstPath]); delFolder(); break; } + } else if ([srcPath isEqualToString:readyFilePath]) { + writeLog([[NSArray arrayWithObjects: @"Skipping ready file ", srcPath, nil] componentsJoinedByString:@""]); } else if ([fileManager fileExistsAtPath:dstPath]) { if (![[NSData dataWithContentsOfFile:srcPath] writeToFile:dstPath atomically:YES]) { writeLog([@"Failed to edit file " stringByAppendingString:dstPath]); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 1694ccc86b..a59e5e4be6 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -29,6 +29,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "localstorage.h" +#include "autoupdater.h" + namespace { Application *mainApp = 0; FileUploader *uploader = 0; @@ -180,29 +182,6 @@ Application::Application(int &argc, char **argv) : PsApplication(argc, argv), } } -void Application::onAppUpdate(const MTPhelp_AppUpdate &response) { - updateRequestId = 0; - - cSetLastUpdateCheck(unixtime()); - Local::writeSettings(); - if (response.type() == mtpc_help_noAppUpdate) { - startUpdateCheck(); - } else { - updateThread = new QThread(); - connect(updateThread, SIGNAL(finished()), updateThread, SLOT(deleteLater())); - updateDownloader = new PsUpdateDownloader(updateThread, response.c_help_appUpdate()); - updateThread->start(); - } -} - -bool Application::onAppUpdateFail() { - updateRequestId = 0; - cSetLastUpdateCheck(unixtime()); - Local::writeSettings(); - startUpdateCheck(); - return true; -} - void Application::updateGotCurrent() { if (!updateReply || updateThread) return; @@ -213,7 +192,7 @@ void Application::updateGotCurrent() { if (currentVersion > AppVersion) { updateThread = new QThread(); connect(updateThread, SIGNAL(finished()), updateThread, SLOT(deleteLater())); - updateDownloader = new PsUpdateDownloader(updateThread, m.captured(2)); + updateDownloader = new UpdateDownloader(updateThread, m.captured(2)); updateThread->start(); } } @@ -540,7 +519,6 @@ void Application::startUpdateCheck(bool forceWait) { updateReply = updateManager.get(checkVersion); connect(updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent())); connect(updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError))); -// updateRequestId = MTP::send(MTPhelp_GetAppUpdate(MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(cApiLang())), rpcDone(&Application::onAppUpdate), rpcFail(&Application::onAppUpdateFail); emit updateChecking(); } else { updateCheckTimer.start((updateInSecs + 5) * 1000); @@ -648,7 +626,7 @@ void Application::socketError(QLocalSocket::LocalSocketError e) { return App::quit(); } - if (!cNoStartUpdate() && psCheckReadyUpdate()) { + if (!cNoStartUpdate() && checkReadyUpdate()) { cSetRestartingUpdate(true); DEBUG_LOG(("Application Info: installing update instead of starting app..")); return App::quit(); diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index 78d58834df..694db88507 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -27,6 +27,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org class MainWidget; class FileUploader; class Translator; +class UpdateDownloader; class Application : public PsApplication, public RPCSender { Q_OBJECT @@ -42,9 +43,6 @@ public: static int32 languageId(); static MainWidget *main(); - void onAppUpdate(const MTPhelp_AppUpdate &response); - bool onAppUpdateFail(); - enum UpdatingState { UpdatingNone, UpdatingDownload, @@ -80,6 +78,12 @@ public: signals: + void updateChecking(); + void updateLatest(); + void updateDownloading(qint64 ready, qint64 total); + void updateReady(); + void updateFailed(); + void peerPhotoDone(PeerId peer); void peerPhotoFail(PeerId peer); @@ -143,7 +147,7 @@ private: QNetworkReply *updateReply; SingleTimer updateCheckTimer; QThread *updateThread; - PsUpdateDownloader *updateDownloader; + UpdateDownloader *updateDownloader; QTimer writeUserConfigTimer; diff --git a/Telegram/SourceFiles/autoupdater.cpp b/Telegram/SourceFiles/autoupdater.cpp new file mode 100644 index 0000000000..ad0eb6f1af --- /dev/null +++ b/Telegram/SourceFiles/autoupdater.cpp @@ -0,0 +1,520 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It 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 General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "application.h" +#include "pspecific.h" +#include "autoupdater.h" + +#ifdef Q_OS_WIN +typedef DWORD VerInt; +typedef WCHAR VerChar; +#else +typedef int VerInt; +typedef wchar_t VerChar; +#endif + +UpdateDownloader::UpdateDownloader(QThread *thread, const QString &url) : already(0), reply(0), full(0) { + updateUrl = url; + moveToThread(thread); + manager.moveToThread(thread); + App::setProxySettings(manager); + + connect(thread, SIGNAL(started()), this, SLOT(start())); + initOutput(); +} + +void UpdateDownloader::initOutput() { + QString fileName; + QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl); + if (m.hasMatch()) { + fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString()); + } + if (fileName.isEmpty()) { + fileName = qsl("tupdate-%1").arg(rand()); + } + QString dirStr = cWorkingDir() + qsl("tupdates/"); + fileName = dirStr + fileName; + QFileInfo file(fileName); + + QDir dir(dirStr); + if (dir.exists()) { + QFileInfoList all = dir.entryInfoList(QDir::Files); + for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) { + if (i->absoluteFilePath() != file.absoluteFilePath()) { + QFile::remove(i->absoluteFilePath()); + } + } + } else { + dir.mkdir(dir.absolutePath()); + } + outputFile.setFileName(fileName); + if (file.exists()) { + uint64 fullSize = file.size(); + if (fullSize < INT_MAX) { + int32 goodSize = (int32)fullSize; + if (goodSize % UpdateChunk) { + goodSize = goodSize - (goodSize % UpdateChunk); + if (goodSize) { + if (outputFile.open(QIODevice::ReadOnly)) { + QByteArray goodData = outputFile.readAll().mid(0, goodSize); + outputFile.close(); + if (outputFile.open(QIODevice::WriteOnly)) { + outputFile.write(goodData); + outputFile.close(); + + QMutexLocker lock(&mutex); + already = goodSize; + } + } + } + } else { + QMutexLocker lock(&mutex); + already = goodSize; + } + } + if (!already) { + QFile::remove(fileName); + } + } +} + +void UpdateDownloader::start() { + sendRequest(); +} + +void UpdateDownloader::sendRequest() { + QNetworkRequest req(updateUrl); + QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-"; + req.setRawHeader("Range", rangeHeaderValue); + req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); + if (reply) reply->deleteLater(); + reply = manager.get(req); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64))); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot())); +} + +void UpdateDownloader::partMetaGot() { + typedef QList Pairs; + Pairs pairs = reply->rawHeaderPairs(); + for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { + if (QString::fromUtf8(i->first).toLower() == "content-range") { + QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second)); + if (m.hasMatch()) { + { + QMutexLocker lock(&mutex); + full = m.captured(1).toInt(); + } + emit App::app()->updateDownloading(already, full); + } + } + } +} + +int32 UpdateDownloader::ready() { + QMutexLocker lock(&mutex); + return already; +} + +int32 UpdateDownloader::size() { + QMutexLocker lock(&mutex); + return full; +} + +void UpdateDownloader::partFinished(qint64 got, qint64 total) { + if (!reply) return; + + QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + if (statusCode.isValid()) { + int status = statusCode.toInt(); + if (status != 200 && status != 206 && status != 416) { + LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status)); + return fatalFail(); + } + } + + if (!already && !full) { + QMutexLocker lock(&mutex); + full = total; + } + DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total)); + + if (!outputFile.isOpen()) { + if (!outputFile.open(QIODevice::Append)) { + LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName())); + return fatalFail(); + } + } + QByteArray r = reply->readAll(); + if (!r.isEmpty()) { + outputFile.write(r); + + QMutexLocker lock(&mutex); + already += r.size(); + } + if (got >= total) { + reply->deleteLater(); + reply = 0; + outputFile.close(); + unpackUpdate(); + } else { + emit App::app()->updateDownloading(already, full); + } +} + +void UpdateDownloader::partFailed(QNetworkReply::NetworkError e) { + if (!reply) return; + + QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + reply->deleteLater(); + reply = 0; + if (statusCode.isValid()) { + int status = statusCode.toInt(); + if (status == 416) { // Requested range not satisfiable + outputFile.close(); + unpackUpdate(); + return; + } + } + LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e)); + emit App::app()->updateFailed(); +} + +void UpdateDownloader::fatalFail() { + clearAll(); + emit App::app()->updateFailed(); +} + +void UpdateDownloader::clearAll() { + psDeleteDir(cWorkingDir() + qsl("tupdates")); +} + +//QString winapiErrorWrap() { +// WCHAR errMsg[2048]; +// DWORD errorCode = GetLastError(); +// LPTSTR errorText = NULL, errorTextDefault = L"(Unknown error)"; +// FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, 0); +// if (!errorText) { +// errorText = errorTextDefault; +// } +// StringCbPrintf(errMsg, sizeof(errMsg), L"Error code: %d, error message: %s", errorCode, errorText); +// if (errorText != errorTextDefault) { +// LocalFree(errorText); +// } +// return QString::fromWCharArray(errMsg); +//} + +void UpdateDownloader::unpackUpdate() { + QByteArray packed; + if (!outputFile.open(QIODevice::ReadOnly)) { + LOG(("Update Error: cant read updates file!")); + return fatalFail(); + } + +#ifdef Q_OS_WIN // use Lzma SDK for win + const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header +#else + const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header +#endif + + QByteArray compressed = outputFile.readAll(); + int32 compressedLen = compressed.size() - hSize; + if (compressedLen <= 0) { + LOG(("Update Error: bad compressed size: %1").arg(compressed.size())); + return fatalFail(); + } + outputFile.close(); + + QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"); + psDeleteDir(tempDirPath); + + QDir tempDir(tempDirPath); + if (tempDir.exists() || QFile(readyFilePath).exists()) { + LOG(("Update Error: cant clear tupdates/temp dir!")); + return fatalFail(); + } + + uchar sha1Buffer[20]; + bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen); + if (!goodSha1) { + LOG(("Update Error: bad SHA1 hash of update file!")); + return fatalFail(); + } + + RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0); + if (!pbKey) { + LOG(("Update Error: cant read public rsa key!")); + return fatalFail(); + } + if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature + RSA_free(pbKey); + LOG(("Update Error: bad RSA signature of update file!")); + return fatalFail(); + } + RSA_free(pbKey); + + QByteArray uncompressed; + + int32 uncompressedLen; + memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen); + uncompressed.resize(uncompressedLen); + + size_t resultLen = uncompressed.size(); +#ifdef Q_OS_WIN // use Lzma SDK for win + SizeT srcLen = compressedLen; + int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE); + if (uncompressRes != SZ_OK) { + LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes)); + return fatalFail(); + } +#else + lzma_stream stream = LZMA_STREAM_INIT; + + lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED); + if (ret != LZMA_OK) { + const char *msg; + switch (ret) { + case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; + case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break; + case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break; + default: msg = "Unknown error, possibly a bug"; break; + } + LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret)); + return fatalFail(); + } + + stream.avail_in = compressedLen; + stream.next_in = (uint8_t*)(compressed.constData() + hSize); + stream.avail_out = resultLen; + stream.next_out = (uint8_t*)uncompressed.data(); + + lzma_ret res = lzma_code(&stream, LZMA_FINISH); + if (stream.avail_in) { + LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen)); + return fatalFail(); + } else if (stream.avail_out) { + LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen)); + return fatalFail(); + } + lzma_end(&stream); + if (res != LZMA_OK && res != LZMA_STREAM_END) { + const char *msg; + switch (res) { + case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; + case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break; + case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break; + case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break; + case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break; + default: msg = "Unknown error, possibly a bug"; break; + } + LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res)); + return fatalFail(); + } +#endif + + tempDir.mkdir(tempDir.absolutePath()); + + quint32 version; + { + QBuffer buffer(&uncompressed); + buffer.open(QIODevice::ReadOnly); + QDataStream stream(&buffer); + stream.setVersion(QDataStream::Qt_5_1); + + stream >> version; + if (stream.status() != QDataStream::Ok) { + LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status())); + return fatalFail(); + } + if (int32(version) <= AppVersion) { + LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion)); + return fatalFail(); + } + + quint32 filesCount; + stream >> filesCount; + if (stream.status() != QDataStream::Ok) { + LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status())); + return fatalFail(); + } + if (!filesCount) { + LOG(("Update Error: update is empty!")); + return fatalFail(); + } + for (uint32 i = 0; i < filesCount; ++i) { + QString relativeName; + quint32 fileSize; + QByteArray fileInnerData; + bool executable = false; + + stream >> relativeName >> fileSize >> fileInnerData; +#if defined Q_OS_MAC || defined Q_OS_LINUX + stream >> executable; +#endif + if (stream.status() != QDataStream::Ok) { + LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status())); + return fatalFail(); + } + if (fileSize != quint32(fileInnerData.size())) { + LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size())); + return fatalFail(); + } + + QFile f(tempDirPath + '/' + relativeName); + if (!QDir().mkpath(QFileInfo(f).absolutePath())) { + LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName)); + return fatalFail(); + } + if (!f.open(QIODevice::WriteOnly)) { + LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName)); + return fatalFail(); + } + if (f.write(fileInnerData) != fileSize) { + f.close(); + LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName)); + return fatalFail(); + } + f.close(); + if (executable) { + QFileDevice::Permissions p = f.permissions(); + p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther; + f.setPermissions(p); + } + } + + // create tdata/version file + tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath()); + std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString(); + + VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar)); + VerChar versionStr[32]; + memcpy(versionStr, versionString.c_str(), versionLen); + + QFile fVersion(tempDirPath + qsl("/tdata/version")); + if (!fVersion.open(QIODevice::WriteOnly)) { + LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version"))); + return fatalFail(); + } + fVersion.write((const char*)&versionNum, sizeof(VerInt)); + fVersion.write((const char*)&versionLen, sizeof(VerInt)); + fVersion.write((const char*)&versionStr[0], versionLen); + fVersion.close(); + } + + QFile readyFile(readyFilePath); + if (readyFile.open(QIODevice::WriteOnly)) { + if (readyFile.write("1", 1)) { + readyFile.close(); + } else { + LOG(("Update Error: cant write ready file '%1'").arg(readyFilePath)); + return fatalFail(); + } + } else { + LOG(("Update Error: cant create ready file '%1'").arg(readyFilePath)); + return fatalFail(); + } + outputFile.remove(); + + emit App::app()->updateReady(); +} + +UpdateDownloader::~UpdateDownloader() { + delete reply; + reply = 0; +} + +bool checkReadyUpdate() { + QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp"); + if (!QFile(readyFilePath).exists()) { + return false; + } + + // check ready version + QString versionPath = readyPath + qsl("/tdata/version"); + { + QFile fVersion(versionPath); + if (!fVersion.open(QIODevice::ReadOnly)) { + LOG(("Update Error: cant read version file '%1'").arg(versionPath)); + UpdateDownloader::clearAll(); + return false; + } + VerInt versionNum; + if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) { + LOG(("Update Error: cant read version from file '%1'").arg(versionPath)); + UpdateDownloader::clearAll(); + return false; + } + fVersion.close(); + if (versionNum <= AppVersion) { + LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion)); + UpdateDownloader::clearAll(); + return false; + } + } + +#ifdef Q_OS_WIN + QString curUpdater = (cExeDir() + qsl("Updater.exe")); + QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater.exe")); +#elif defined Q_OS_MAC + QString curUpdater = (cExeDir() + cExeName() + qsl("/Contents/Frameworks/Updater")); + QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Telegram.app/Contents/Frameworks/Updater")); +#elif defined Q_OS_LINUX + QString curUpdater = (cExeDir() + qsl("Updater")); + QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater")); +#endif + if (!updater.exists()) { + QFileInfo current(curUpdater); + if (!current.exists()) { + UpdateDownloader::clearAll(); + return false; + } + if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) { + UpdateDownloader::clearAll(); + return false; + } + } +#ifdef Q_OS_WIN + if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) { + DWORD errorCode = GetLastError(); + if (errorCode == ERROR_ACCESS_DENIED) { // we are in write-protected dir, like Program Files + cSetWriteProtected(true); + return true; + } else { + UpdateDownloader::clearAll(); + return false; + } + } + if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) { + UpdateDownloader::clearAll(); + return false; + } +#elif defined Q_OS_MAC + QDir().mkpath(QFileInfo(curUpdater).absolutePath()); + DEBUG_LOG(("Update Info: moving %1 to %2..").arg(updater.absoluteFilePath()).arg(curUpdater)); + if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) { + UpdateDownloader::clearAll(); + return false; + } +#elif defined Q_OS_LINUX + if (!moveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) { + UpdateDownloader::clearAll(); + return false; + } +#endif + return true; +} diff --git a/Telegram/SourceFiles/autoupdater.h b/Telegram/SourceFiles/autoupdater.h new file mode 100644 index 0000000000..097f335ad2 --- /dev/null +++ b/Telegram/SourceFiles/autoupdater.h @@ -0,0 +1,62 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It 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 General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include +#include +#include + +class UpdateDownloader : public QObject { + Q_OBJECT + +public: + UpdateDownloader(QThread *thread, const QString &url); + + void unpackUpdate(); + + int32 ready(); + int32 size(); + + static void clearAll(); + + ~UpdateDownloader(); + +public slots: + + void start(); + void partMetaGot(); + void partFinished(qint64 got, qint64 total); + void partFailed(QNetworkReply::NetworkError e); + void sendRequest(); + +private: + void initOutput(); + + void fatalFail(); + + QString updateUrl; + QNetworkAccessManager manager; + QNetworkReply *reply; + int32 already, full; + QFile outputFile; + + QMutex mutex; + +}; + +bool checkReadyUpdate(); diff --git a/Telegram/SourceFiles/pspecific_linux.cpp b/Telegram/SourceFiles/pspecific_linux.cpp index b5bbf30c51..5b9ffef100 100644 --- a/Telegram/SourceFiles/pspecific_linux.cpp +++ b/Telegram/SourceFiles/pspecific_linux.cpp @@ -946,183 +946,6 @@ PsApplication::~PsApplication() { _psEventFilter = 0; } -PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update) : reply(0), already(0), full(0) { - updateUrl = qs(update.vurl); - moveToThread(thread); - manager.moveToThread(thread); - App::setProxySettings(manager); - - connect(thread, SIGNAL(started()), this, SLOT(start())); - initOutput(); -} - -PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) { - updateUrl = url; - moveToThread(thread); - manager.moveToThread(thread); - App::setProxySettings(manager); - - connect(thread, SIGNAL(started()), this, SLOT(start())); - initOutput(); -} - -void PsUpdateDownloader::initOutput() { - QString fileName; - QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl); - if (m.hasMatch()) { - fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString()); - } - if (fileName.isEmpty()) { - fileName = qsl("tupdate-%1").arg(rand()); - } - QString dirStr = cWorkingDir() + qsl("tupdates/"); - fileName = dirStr + fileName; - QFileInfo file(fileName); - - QDir dir(dirStr); - if (dir.exists()) { - QFileInfoList all = dir.entryInfoList(QDir::Files); - for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) { - if (i->absoluteFilePath() != file.absoluteFilePath()) { - QFile::remove(i->absoluteFilePath()); - } - } - } else { - dir.mkdir(dir.absolutePath()); - } - outputFile.setFileName(fileName); - if (file.exists()) { - uint64 fullSize = file.size(); - if (fullSize < INT_MAX) { - int32 goodSize = (int32)fullSize; - if (goodSize % UpdateChunk) { - goodSize = goodSize - (goodSize % UpdateChunk); - if (goodSize) { - if (outputFile.open(QIODevice::ReadOnly)) { - QByteArray goodData = outputFile.readAll().mid(0, goodSize); - outputFile.close(); - if (outputFile.open(QIODevice::WriteOnly)) { - outputFile.write(goodData); - outputFile.close(); - - QMutexLocker lock(&mutex); - already = goodSize; - } - } - } - } else { - QMutexLocker lock(&mutex); - already = goodSize; - } - } - if (!already) { - QFile::remove(fileName); - } - } -} - -void PsUpdateDownloader::start() { - sendRequest(); -} - -void PsUpdateDownloader::sendRequest() { - QNetworkRequest req(updateUrl); - QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";// + QByteArray::number(already + cUpdateChunk() - 1); - req.setRawHeader("Range", rangeHeaderValue); - req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); - if (reply) reply->deleteLater(); - reply = manager.get(req); - connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64))); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError))); - connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot())); -} - -void PsUpdateDownloader::partMetaGot() { - typedef QList Pairs; - Pairs pairs = reply->rawHeaderPairs(); - for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { - if (QString::fromUtf8(i->first).toLower() == "content-range") { - QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second)); - if (m.hasMatch()) { - { - QMutexLocker lock(&mutex); - full = m.captured(1).toInt(); - } - emit App::app()->updateDownloading(already, full); - } - } - } -} - -int32 PsUpdateDownloader::ready() { - QMutexLocker lock(&mutex); - return already; -} - -int32 PsUpdateDownloader::size() { - QMutexLocker lock(&mutex); - return full; -} - -void PsUpdateDownloader::partFinished(qint64 got, qint64 total) { - if (!reply) return; - - QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - if (statusCode.isValid()) { - int status = statusCode.toInt(); - if (status != 200 && status != 206 && status != 416) { - LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status)); - return fatalFail(); - } - } - - if (!already && !full) { - QMutexLocker lock(&mutex); - full = total; - } - DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total)); - - if (!outputFile.isOpen()) { - if (!outputFile.open(QIODevice::Append)) { - LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName())); - return fatalFail(); - } - } - QByteArray r = reply->readAll(); - if (!r.isEmpty()) { - outputFile.write(r); - - QMutexLocker lock(&mutex); - already += r.size(); - } - if (got >= total) { - reply->deleteLater(); - reply = 0; - outputFile.close(); - unpackUpdate(); - } else { - emit App::app()->updateDownloading(already, full); - } -} - -void PsUpdateDownloader::partFailed(QNetworkReply::NetworkError e) { - if (!reply) return; - - QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - reply->deleteLater(); - reply = 0; - if (statusCode.isValid()) { - int status = statusCode.toInt(); - if (status == 416) { // Requested range not satisfiable - outputFile.close(); - unpackUpdate(); - return; - } - } - LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e)); - emit App::app()->updateFailed(); -} - bool _removeDirectory(const QString &path) { // from http://stackoverflow.com/questions/2256945/removing-a-non-empty-directory-programmatically-in-c-or-c QByteArray pathRaw = QFile::encodeName(path); DIR *d = opendir(pathRaw.constData()); @@ -1154,236 +977,8 @@ bool _removeDirectory(const QString &path) { // from http://stackoverflow.com/qu return !rmdir(pathRaw.constData()); } -void PsUpdateDownloader::deleteDir(const QString &dir) { - _removeDirectory(dir); -} - -void PsUpdateDownloader::fatalFail() { - clearAll(); - emit App::app()->updateFailed(); -} - -void PsUpdateDownloader::clearAll() { - deleteDir(cWorkingDir() + qsl("tupdates")); -} - -#ifdef Q_OS_WIN -typedef DWORD VerInt; -typedef WCHAR VerChar; -#else -typedef int VerInt; -typedef wchar_t VerChar; -#endif - -void PsUpdateDownloader::unpackUpdate() { - QByteArray packed; - if (!outputFile.open(QIODevice::ReadOnly)) { - LOG(("Update Error: cant read updates file!")); - return fatalFail(); - } -#ifdef Q_OS_WIN // use Lzma SDK for win - const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header -#else - const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header -#endif - QByteArray compressed = outputFile.readAll(); - int32 compressedLen = compressed.size() - hSize; - if (compressedLen <= 0) { - LOG(("Update Error: bad compressed size: %1").arg(compressed.size())); - return fatalFail(); - } - outputFile.close(); - - QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyDirPath = cWorkingDir() + qsl("tupdates/ready"); - deleteDir(tempDirPath); - deleteDir(readyDirPath); - - QDir tempDir(tempDirPath), readyDir(readyDirPath); - if (tempDir.exists() || readyDir.exists()) { - LOG(("Update Error: cant clear tupdates/temp or tupdates/ready dir!")); - return fatalFail(); - } - - uchar sha1Buffer[20]; - bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen); - if (!goodSha1) { - LOG(("Update Error: bad SHA1 hash of update file!")); - return fatalFail(); - } - - RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0); - if (!pbKey) { - LOG(("Update Error: cant read public rsa key!")); - return fatalFail(); - } - if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature - RSA_free(pbKey); - LOG(("Update Error: bad RSA signature of update file!")); - return fatalFail(); - } - RSA_free(pbKey); - - QByteArray uncompressed; - - int32 uncompressedLen; - memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen); - uncompressed.resize(uncompressedLen); - - size_t resultLen = uncompressed.size(); -#ifdef Q_OS_WIN // use Lzma SDK for win - SizeT srcLen = compressedLen; - int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE); - if (uncompressRes != SZ_OK) { - LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes)); - return fatalFail(); - } -#else - lzma_stream stream = LZMA_STREAM_INIT; - - lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED); - if (ret != LZMA_OK) { - const char *msg; - switch (ret) { - case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; - case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break; - case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break; - default: msg = "Unknown error, possibly a bug"; break; - } - LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret)); - return fatalFail(); - } - - stream.avail_in = compressedLen; - stream.next_in = (uint8_t*)(compressed.constData() + hSize); - stream.avail_out = resultLen; - stream.next_out = (uint8_t*)uncompressed.data(); - - lzma_ret res = lzma_code(&stream, LZMA_FINISH); - if (stream.avail_in) { - LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen)); - return fatalFail(); - } else if (stream.avail_out) { - LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen)); - return fatalFail(); - } - lzma_end(&stream); - if (res != LZMA_OK && res != LZMA_STREAM_END) { - const char *msg; - switch (res) { - case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; - case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break; - case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break; - case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break; - case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break; - default: msg = "Unknown error, possibly a bug"; break; - } - LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res)); - return fatalFail(); - } -#endif - - tempDir.mkdir(tempDir.absolutePath()); - - quint32 version; - { - QBuffer buffer(&uncompressed); - buffer.open(QIODevice::ReadOnly); - QDataStream stream(&buffer); - stream.setVersion(QDataStream::Qt_5_1); - - stream >> version; - if (stream.status() != QDataStream::Ok) { - LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status())); - return fatalFail(); - } - if (int32(version) <= AppVersion) { - LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion)); - return fatalFail(); - } - - quint32 filesCount; - stream >> filesCount; - if (stream.status() != QDataStream::Ok) { - LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status())); - return fatalFail(); - } - if (!filesCount) { - LOG(("Update Error: update is empty!")); - return fatalFail(); - } - for (uint32 i = 0; i < filesCount; ++i) { - QString relativeName; - quint32 fileSize; - QByteArray fileInnerData; - bool executable = false; - - stream >> relativeName >> fileSize >> fileInnerData; -#if defined Q_OS_MAC || defined Q_OS_LINUX - stream >> executable; -#endif - if (stream.status() != QDataStream::Ok) { - LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status())); - return fatalFail(); - } - if (fileSize != quint32(fileInnerData.size())) { - LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size())); - return fatalFail(); - } - - QFile f(tempDirPath + '/' + relativeName); - if (!QDir().mkpath(QFileInfo(f).absolutePath())) { - LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName)); - return fatalFail(); - } - if (!f.open(QIODevice::WriteOnly)) { - LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName)); - return fatalFail(); - } - if (f.write(fileInnerData) != fileSize) { - f.close(); - LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName)); - return fatalFail(); - } - f.close(); - if (executable) { - QFileDevice::Permissions p = f.permissions(); - p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther; - f.setPermissions(p); - } - } - - // create tdata/version file - tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath()); - std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString(); - - VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar)); - VerChar versionStr[32]; - memcpy(versionStr, versionString.c_str(), versionLen); - - QFile fVersion(tempDirPath + qsl("/tdata/version")); - if (!fVersion.open(QIODevice::WriteOnly)) { - LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version"))); - return fatalFail(); - } - fVersion.write((const char*)&versionNum, sizeof(VerInt)); - fVersion.write((const char*)&versionLen, sizeof(VerInt)); - fVersion.write((const char*)&versionStr[0], versionLen); - fVersion.close(); - } - - if (!tempDir.rename(tempDir.absolutePath(), readyDir.absolutePath())) { - LOG(("Update Error: cant rename temp dir '%1' to ready dir '%2'").arg(tempDir.absolutePath()).arg(readyDir.absolutePath())); - return fatalFail(); - } - deleteDir(tempDirPath); - outputFile.remove(); - - emit App::app()->updateReady(); -} - -PsUpdateDownloader::~PsUpdateDownloader() { - delete reply; - reply = 0; +void psDeleteDir(const QString &dir) { + _removeDirectory(dir); } namespace { @@ -1497,129 +1092,6 @@ int psFixPrevious() { return 0; } -#ifdef Q_OS_LINUX -bool moveFile(const char *from, const char *to) { - FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); - if (!ffrom) { - if (fto) fclose(fto); - return false; - } - if (!fto) { - fclose(ffrom); - return false; - } - static const int BufSize = 65536; - char buf[BufSize]; - while (size_t size = fread(buf, 1, BufSize, ffrom)) { - fwrite(buf, 1, size, fto); - } - - struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c - //let's say this wont fail since you already worked OK on that fp - if (fstat(fileno(ffrom), &fst) != 0) { - fclose(ffrom); - fclose(fto); - return false; - } - //update to the same uid/gid - if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { - fclose(ffrom); - fclose(fto); - return false; - } - //update the permissions - if (fchmod(fileno(fto), fst.st_mode) != 0) { - fclose(ffrom); - fclose(fto); - return false; - } - - fclose(ffrom); - fclose(fto); - - if (unlink(from)) { - return false; - } - - return true; -} -#endif - -bool psCheckReadyUpdate() { - QString readyPath = cWorkingDir() + qsl("tupdates/ready"); - if (!QDir(readyPath).exists()) { - return false; - } - - // check ready version - QString versionPath = readyPath + qsl("/tdata/version"); - { - QFile fVersion(versionPath); - if (!fVersion.open(QIODevice::ReadOnly)) { - LOG(("Update Error: cant read version file '%1'").arg(versionPath)); - PsUpdateDownloader::clearAll(); - return false; - } - VerInt versionNum; - if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) { - LOG(("Update Error: cant read version from file '%1'").arg(versionPath)); - PsUpdateDownloader::clearAll(); - return false; - } - fVersion.close(); - if (versionNum <= AppVersion) { - LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion)); - PsUpdateDownloader::clearAll(); - return false; - } - } - -#ifdef Q_OS_WIN - QString curUpdater = (cExeDir() + "Updater.exe"); - QFileInfo updater(cWorkingDir() + "tupdates/ready/Updater.exe"); -#elif defined Q_OS_MAC - QString curUpdater = (cExeDir() + "Telegram.app/Contents/Frameworks/Updater"); - QFileInfo updater(cWorkingDir() + "tupdates/ready/Telegram.app/Contents/Frameworks/Updater"); -#elif defined Q_OS_LINUX - QString curUpdater = (cExeDir() + "Updater"); - QFileInfo updater(cWorkingDir() + "tupdates/ready/Updater"); -#endif - if (!updater.exists()) { - QFileInfo current(curUpdater); - if (!current.exists()) { - PsUpdateDownloader::clearAll(); - return false; - } - if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) { - PsUpdateDownloader::clearAll(); - return false; - } - } -#ifdef Q_OS_WIN - if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) { - PsUpdateDownloader::clearAll(); - return false; - } - if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) { - PsUpdateDownloader::clearAll(); - return false; - } -#elif defined Q_OS_MAC - QFileInfo to(curUpdater); - QDir().mkpath(to.absolutePath()); - if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) { - PsUpdateDownloader::clearAll(); - return false; - } -#elif defined Q_OS_LINUX - if (!moveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) { - PsUpdateDownloader::clearAll(); - return false; - } -#endif - return true; -} - void psPostprocessFile(const QString &name) { } @@ -1781,8 +1253,7 @@ bool _execUpdater(bool update = true) { void psExecUpdater() { if (!_execUpdater()) { - QString readyPath = cWorkingDir() + qsl("tupdates/ready"); - PsUpdateDownloader::deleteDir(readyPath); + psDeleteDir(cWorkingDir() + qsl("tupdates/temp")); } } @@ -1802,3 +1273,49 @@ void psSendToMenu(bool send, bool silent) { void psUpdateOverlayed(QWidget *widget) { } + +bool linuxMoveFile(const char *from, const char *to) { + FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); + if (!ffrom) { + if (fto) fclose(fto); + return false; + } + if (!fto) { + fclose(ffrom); + return false; + } + static const int BufSize = 65536; + char buf[BufSize]; + while (size_t size = fread(buf, 1, BufSize, ffrom)) { + fwrite(buf, 1, size, fto); + } + + struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c + //let's say this wont fail since you already worked OK on that fp + if (fstat(fileno(ffrom), &fst) != 0) { + fclose(ffrom); + fclose(fto); + return false; + } + //update to the same uid/gid + if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { + fclose(ffrom); + fclose(fto); + return false; + } + //update the permissions + if (fchmod(fileno(fto), fst.st_mode) != 0) { + fclose(ffrom); + fclose(fto); + return false; + } + + fclose(ffrom); + fclose(fto); + + if (unlink(from)) { + return false; + } + + return true; +} diff --git a/Telegram/SourceFiles/pspecific_linux.h b/Telegram/SourceFiles/pspecific_linux.h index d34bab2eaf..580fefcec4 100644 --- a/Telegram/SourceFiles/pspecific_linux.h +++ b/Telegram/SourceFiles/pspecific_linux.h @@ -116,55 +116,9 @@ public: void psInstallEventFilter(); ~PsApplication(); -signals: - - void updateChecking(); - void updateLatest(); - void updateDownloading(qint64 ready, qint64 total); - void updateReady(); - void updateFailed(); - }; -class PsUpdateDownloader : public QObject { - Q_OBJECT - -public: - PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update); - PsUpdateDownloader(QThread *thread, const QString &url); - - void unpackUpdate(); - - int32 ready(); - int32 size(); - - static void deleteDir(const QString &dir); - static void clearAll(); - - ~PsUpdateDownloader(); - -public slots: - - void start(); - void partMetaGot(); - void partFinished(qint64 got, qint64 total); - void partFailed(QNetworkReply::NetworkError e); - void sendRequest(); - -private: - void initOutput(); - - void fatalFail(); - - QString updateUrl; - QNetworkAccessManager manager; - QNetworkReply *reply; - int32 already, full; - QFile outputFile; - - QMutex mutex; - -}; +void psDeleteDir(const QString &dir); void psUserActionDone(); bool psIdleSupported(); @@ -194,7 +148,6 @@ void psBringToBack(QWidget *w); int psCleanup(); int psFixPrevious(); -bool psCheckReadyUpdate(); void psExecUpdater(); void psExecTelegram(); @@ -212,3 +165,5 @@ void psUpdateOverlayed(QWidget *widget); inline QString psConvertFileUrl(const QString &url) { return url; } + +bool linuxMoveFile(const char *from, const char *to); diff --git a/Telegram/SourceFiles/pspecific_mac.cpp b/Telegram/SourceFiles/pspecific_mac.cpp index ed3d8c71d1..f957384e28 100644 --- a/Telegram/SourceFiles/pspecific_mac.cpp +++ b/Telegram/SourceFiles/pspecific_mac.cpp @@ -522,415 +522,10 @@ PsApplication::~PsApplication() { _psEventFilter = 0; } -PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update) : reply(0), already(0), full(0) { - updateUrl = qs(update.vurl); - moveToThread(thread); - manager.moveToThread(thread); - App::setProxySettings(manager); - - connect(thread, SIGNAL(started()), this, SLOT(start())); - initOutput(); -} - -PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) { - updateUrl = url; - moveToThread(thread); - manager.moveToThread(thread); - App::setProxySettings(manager); - - connect(thread, SIGNAL(started()), this, SLOT(start())); - initOutput(); -} - -void PsUpdateDownloader::initOutput() { - QString fileName; - QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl); - if (m.hasMatch()) { - fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString()); - } - if (fileName.isEmpty()) { - fileName = qsl("tupdate-%1").arg(rand()); - } - QString dirStr = cWorkingDir() + qsl("tupdates/"); - fileName = dirStr + fileName; - QFileInfo file(fileName); - - QDir dir(dirStr); - if (dir.exists()) { - QFileInfoList all = dir.entryInfoList(QDir::Files); - for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) { - if (i->absoluteFilePath() != file.absoluteFilePath()) { - QFile::remove(i->absoluteFilePath()); - } - } - } else { - dir.mkdir(dir.absolutePath()); - } - outputFile.setFileName(fileName); - if (file.exists()) { - uint64 fullSize = file.size(); - if (fullSize < INT_MAX) { - int32 goodSize = (int32)fullSize; - if (goodSize % UpdateChunk) { - goodSize = goodSize - (goodSize % UpdateChunk); - if (goodSize) { - if (outputFile.open(QIODevice::ReadOnly)) { - QByteArray goodData = outputFile.readAll().mid(0, goodSize); - outputFile.close(); - if (outputFile.open(QIODevice::WriteOnly)) { - outputFile.write(goodData); - outputFile.close(); - - QMutexLocker lock(&mutex); - already = goodSize; - } - } - } - } else { - QMutexLocker lock(&mutex); - already = goodSize; - } - } - if (!already) { - QFile::remove(fileName); - } - } -} - -void PsUpdateDownloader::start() { - sendRequest(); -} - -void PsUpdateDownloader::sendRequest() { - QNetworkRequest req(updateUrl); - QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";// + QByteArray::number(already + cUpdateChunk() - 1); - req.setRawHeader("Range", rangeHeaderValue); - req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); - if (reply) reply->deleteLater(); - reply = manager.get(req); - connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64))); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError))); - connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot())); -} - -void PsUpdateDownloader::partMetaGot() { - typedef QList Pairs; - Pairs pairs = reply->rawHeaderPairs(); - for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { - if (QString::fromUtf8(i->first).toLower() == "content-range") { - QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second)); - if (m.hasMatch()) { - { - QMutexLocker lock(&mutex); - full = m.captured(1).toInt(); - } - emit App::app()->updateDownloading(already, full); - } - } - } -} - -int32 PsUpdateDownloader::ready() { - QMutexLocker lock(&mutex); - return already; -} - -int32 PsUpdateDownloader::size() { - QMutexLocker lock(&mutex); - return full; -} - -void PsUpdateDownloader::partFinished(qint64 got, qint64 total) { - if (!reply) return; - - QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - if (statusCode.isValid()) { - int status = statusCode.toInt(); - if (status != 200 && status != 206 && status != 416) { - LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status)); - return fatalFail(); - } - } - - if (!already && !full) { - QMutexLocker lock(&mutex); - full = total; - } - DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total)); - - if (!outputFile.isOpen()) { - if (!outputFile.open(QIODevice::Append)) { - LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName())); - return fatalFail(); - } - } - QByteArray r = reply->readAll(); - if (!r.isEmpty()) { - outputFile.write(r); - - QMutexLocker lock(&mutex); - already += r.size(); - } - if (got >= total) { - reply->deleteLater(); - reply = 0; - outputFile.close(); - unpackUpdate(); - } else { - emit App::app()->updateDownloading(already, full); - } -} - -void PsUpdateDownloader::partFailed(QNetworkReply::NetworkError e) { - if (!reply) return; - - QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - reply->deleteLater(); - reply = 0; - if (statusCode.isValid()) { - int status = statusCode.toInt(); - if (status == 416) { // Requested range not satisfiable - outputFile.close(); - unpackUpdate(); - return; - } - } - LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e)); - emit App::app()->updateFailed(); -} - -void PsUpdateDownloader::deleteDir(const QString &dir) { +void psDeleteDir(const QString &dir) { objc_deleteDir(dir); } -void PsUpdateDownloader::fatalFail() { - clearAll(); - emit App::app()->updateFailed(); -} - -void PsUpdateDownloader::clearAll() { - deleteDir(cWorkingDir() + qsl("tupdates")); -} - -#ifdef Q_OS_WIN -typedef DWORD VerInt; -typedef WCHAR VerChar; -#else -typedef int VerInt; -typedef wchar_t VerChar; -#endif - -void PsUpdateDownloader::unpackUpdate() { - QByteArray packed; - if (!outputFile.open(QIODevice::ReadOnly)) { - LOG(("Update Error: cant read updates file!")); - return fatalFail(); - } -#ifdef Q_OS_WIN // use Lzma SDK for win - const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header -#else - const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header -#endif - QByteArray compressed = outputFile.readAll(); - int32 compressedLen = compressed.size() - hSize; - if (compressedLen <= 0) { - LOG(("Update Error: bad compressed size: %1").arg(compressed.size())); - return fatalFail(); - } - outputFile.close(); - - QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyDirPath = cWorkingDir() + qsl("tupdates/ready"); - deleteDir(tempDirPath); - deleteDir(readyDirPath); - - QDir tempDir(tempDirPath), readyDir(readyDirPath); - if (tempDir.exists() || readyDir.exists()) { - LOG(("Update Error: cant clear tupdates/temp or tupdates/ready dir!")); - return fatalFail(); - } - - uchar sha1Buffer[20]; - bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen); - if (!goodSha1) { - LOG(("Update Error: bad SHA1 hash of update file!")); - return fatalFail(); - } - - RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0); - if (!pbKey) { - LOG(("Update Error: cant read public rsa key!")); - return fatalFail(); - } - if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature - RSA_free(pbKey); - LOG(("Update Error: bad RSA signature of update file!")); - return fatalFail(); - } - RSA_free(pbKey); - - QByteArray uncompressed; - - int32 uncompressedLen; - memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen); - uncompressed.resize(uncompressedLen); - - size_t resultLen = uncompressed.size(); -#ifdef Q_OS_WIN // use Lzma SDK for win - SizeT srcLen = compressedLen; - int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE); - if (uncompressRes != SZ_OK) { - LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes)); - return fatalFail(); - } -#else - lzma_stream stream = LZMA_STREAM_INIT; - - lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED); - if (ret != LZMA_OK) { - const char *msg; - switch (ret) { - case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; - case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break; - case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break; - default: msg = "Unknown error, possibly a bug"; break; - } - LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret)); - return fatalFail(); - } - - stream.avail_in = compressedLen; - stream.next_in = (uint8_t*)(compressed.constData() + hSize); - stream.avail_out = resultLen; - stream.next_out = (uint8_t*)uncompressed.data(); - - lzma_ret res = lzma_code(&stream, LZMA_FINISH); - if (stream.avail_in) { - LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen)); - return fatalFail(); - } else if (stream.avail_out) { - LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen)); - return fatalFail(); - } - lzma_end(&stream); - if (res != LZMA_OK && res != LZMA_STREAM_END) { - const char *msg; - switch (res) { - case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break; - case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break; - case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break; - case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break; - case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break; - default: msg = "Unknown error, possibly a bug"; break; - } - LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res)); - return fatalFail(); - } -#endif - - tempDir.mkdir(tempDir.absolutePath()); - - quint32 version; - { - QBuffer buffer(&uncompressed); - buffer.open(QIODevice::ReadOnly); - QDataStream stream(&buffer); - stream.setVersion(QDataStream::Qt_5_1); - - stream >> version; - if (stream.status() != QDataStream::Ok) { - LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status())); - return fatalFail(); - } - if (version <= AppVersion) { - LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion)); - return fatalFail(); - } - - quint32 filesCount; - stream >> filesCount; - if (stream.status() != QDataStream::Ok) { - LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status())); - return fatalFail(); - } - if (!filesCount) { - LOG(("Update Error: update is empty!")); - return fatalFail(); - } - for (uint32 i = 0; i < filesCount; ++i) { - QString relativeName; - quint32 fileSize; - QByteArray fileInnerData; - bool executable = false; - - stream >> relativeName >> fileSize >> fileInnerData; -#if defined Q_OS_MAC || defined Q_OS_LINUX - stream >> executable; -#endif - if (stream.status() != QDataStream::Ok) { - LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status())); - return fatalFail(); - } - if (fileSize != quint32(fileInnerData.size())) { - LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size())); - return fatalFail(); - } - - QFile f(tempDirPath + '/' + relativeName); - if (!QDir().mkpath(QFileInfo(f).absolutePath())) { - LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName)); - return fatalFail(); - } - if (!f.open(QIODevice::WriteOnly)) { - LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName)); - return fatalFail(); - } - if (f.write(fileInnerData) != fileSize) { - f.close(); - LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName)); - return fatalFail(); - } - f.close(); - if (executable) { - QFileDevice::Permissions p = f.permissions(); - p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther; - f.setPermissions(p); - } - } - - // create tdata/version file - tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath()); - std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString(); - - VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar)); - VerChar versionStr[32]; - memcpy(versionStr, versionString.c_str(), versionLen); - - QFile fVersion(tempDirPath + qsl("/tdata/version")); - if (!fVersion.open(QIODevice::WriteOnly)) { - LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version"))); - return fatalFail(); - } - fVersion.write((const char*)&versionNum, sizeof(VerInt)); - fVersion.write((const char*)&versionLen, sizeof(VerInt)); - fVersion.write((const char*)&versionStr[0], versionLen); - fVersion.close(); - } - - if (!tempDir.rename(tempDir.absolutePath(), readyDir.absolutePath())) { - LOG(("Update Error: cant rename temp dir '%1' to ready dir '%2'").arg(tempDir.absolutePath()).arg(readyDir.absolutePath())); - return fatalFail(); - } - deleteDir(tempDirPath); - outputFile.remove(); - - emit App::app()->updateReady(); -} - -PsUpdateDownloader::~PsUpdateDownloader() { - delete reply; - reply = 0; -} - namespace { uint64 _lastUserAction = 0; } @@ -1029,73 +624,6 @@ int psFixPrevious() { return 0; } -bool psCheckReadyUpdate() { - QString readyPath = cWorkingDir() + qsl("tupdates/ready"); - if (!QDir(readyPath).exists()) { - return false; - } - - // check ready version - QString versionPath = readyPath + qsl("/tdata/version"); - { - QFile fVersion(versionPath); - if (!fVersion.open(QIODevice::ReadOnly)) { - LOG(("Update Error: cant read version file '%1'").arg(versionPath)); - PsUpdateDownloader::clearAll(); - return false; - } - VerInt versionNum; - if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) { - LOG(("Update Error: cant read version from file '%1'").arg(versionPath)); - PsUpdateDownloader::clearAll(); - return false; - } - fVersion.close(); - if (versionNum <= AppVersion) { - LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion)); - PsUpdateDownloader::clearAll(); - return false; - } - } - -#ifdef Q_OS_WIN - QString curUpdater = (cExeDir() + qsl("Updater.exe")); - QFileInfo updater(cWorkingDir() + qsl("tupdates/ready/Updater.exe")); -#elif defined Q_OS_MAC - QString curUpdater = (cExeDir() + cExeName() + qsl("/Contents/Frameworks/Updater")); - QFileInfo updater(cWorkingDir() + qsl("tupdates/ready/Telegram.app/Contents/Frameworks/Updater")); -#endif - if (!updater.exists()) { - QFileInfo current(curUpdater); - if (!current.exists()) { - PsUpdateDownloader::clearAll(); - return false; - } - if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) { - PsUpdateDownloader::clearAll(); - return false; - } - } -#ifdef Q_OS_WIN - if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) { - PsUpdateDownloader::clearAll(); - return false; - } - if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) { - PsUpdateDownloader::clearAll(); - return false; - } -#elif defined Q_OS_MAC - QDir().mkpath(QFileInfo(curUpdater).absolutePath()); - DEBUG_LOG(("Update Info: moving %1 to %2..").arg(updater.absoluteFilePath()).arg(curUpdater)); - if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) { - PsUpdateDownloader::clearAll(); - return false; - } -#endif - return true; -} - bool psShowOpenWithMenu(int x, int y, const QString &file) { return objc_showOpenWithMenu(x, y, file); } @@ -1125,8 +653,7 @@ void psRegisterCustomScheme() { void psExecUpdater() { if (!objc_execUpdater()) { - QString readyPath = cWorkingDir() + qsl("tupdates/ready"); - PsUpdateDownloader::deleteDir(readyPath); + psDeleteDir(cWorkingDir() + qsl("tupdates/temp")); } } diff --git a/Telegram/SourceFiles/pspecific_mac.h b/Telegram/SourceFiles/pspecific_mac.h index 448930ca3e..ffca8718af 100644 --- a/Telegram/SourceFiles/pspecific_mac.h +++ b/Telegram/SourceFiles/pspecific_mac.h @@ -144,55 +144,9 @@ public: void psInstallEventFilter(); ~PsApplication(); -signals: - - void updateChecking(); - void updateLatest(); - void updateDownloading(qint64 ready, qint64 total); - void updateReady(); - void updateFailed(); - }; -class PsUpdateDownloader : public QObject { - Q_OBJECT - -public: - PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update); - PsUpdateDownloader(QThread *thread, const QString &url); - - void unpackUpdate(); - - int32 ready(); - int32 size(); - - static void deleteDir(const QString &dir); - static void clearAll(); - - ~PsUpdateDownloader(); - -public slots: - - void start(); - void partMetaGot(); - void partFinished(qint64 got, qint64 total); - void partFailed(QNetworkReply::NetworkError e); - void sendRequest(); - -private: - void initOutput(); - - void fatalFail(); - - QString updateUrl; - QNetworkAccessManager manager; - QNetworkReply *reply; - int32 already, full; - QFile outputFile; - - QMutex mutex; - -}; +void psDeleteDir(const QString &dir); void psUserActionDone(); bool psIdleSupported(); @@ -222,7 +176,6 @@ void psBringToBack(QWidget *w); int psCleanup(); int psFixPrevious(); -bool psCheckReadyUpdate(); void psExecUpdater(); void psExecTelegram(); diff --git a/Telegram/SourceFiles/pspecific_wnd.cpp b/Telegram/SourceFiles/pspecific_wnd.cpp index abebf74202..15ab2bf9d5 100644 --- a/Telegram/SourceFiles/pspecific_wnd.cpp +++ b/Telegram/SourceFiles/pspecific_wnd.cpp @@ -1385,184 +1385,7 @@ PsApplication::~PsApplication() { _psEventFilter = 0; } -PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update) : already(0), reply(0), full(0) { - updateUrl = qs(update.vurl); - moveToThread(thread); - manager.moveToThread(thread); - App::setProxySettings(manager); - - connect(thread, SIGNAL(started()), this, SLOT(start())); - initOutput(); -} - -PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const QString &url) : already(0), reply(0), full(0) { - updateUrl = url; - moveToThread(thread); - manager.moveToThread(thread); - App::setProxySettings(manager); - - connect(thread, SIGNAL(started()), this, SLOT(start())); - initOutput(); -} - -void PsUpdateDownloader::initOutput() { - QString fileName; - QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl); - if (m.hasMatch()) { - fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString()); - } - if (fileName.isEmpty()) { - fileName = qsl("tupdate-%1").arg(rand()); - } - QString dirStr = cWorkingDir() + qsl("tupdates/"); - fileName = dirStr + fileName; - QFileInfo file(fileName); - - QDir dir(dirStr); - if (dir.exists()) { - QFileInfoList all = dir.entryInfoList(QDir::Files); - for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) { - if (i->absoluteFilePath() != file.absoluteFilePath()) { - QFile::remove(i->absoluteFilePath()); - } - } - } else { - dir.mkdir(dir.absolutePath()); - } - outputFile.setFileName(fileName); - if (file.exists()) { - uint64 fullSize = file.size(); - if (fullSize < INT_MAX) { - int32 goodSize = (int32)fullSize; - if (goodSize % UpdateChunk) { - goodSize = goodSize - (goodSize % UpdateChunk); - if (goodSize) { - if (outputFile.open(QIODevice::ReadOnly)) { - QByteArray goodData = outputFile.readAll().mid(0, goodSize); - outputFile.close(); - if (outputFile.open(QIODevice::WriteOnly)) { - outputFile.write(goodData); - outputFile.close(); - - QMutexLocker lock(&mutex); - already = goodSize; - } - } - } - } else { - QMutexLocker lock(&mutex); - already = goodSize; - } - } - if (!already) { - QFile::remove(fileName); - } - } -} - -void PsUpdateDownloader::start() { - sendRequest(); -} - -void PsUpdateDownloader::sendRequest() { - QNetworkRequest req(updateUrl); - QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";// + QByteArray::number(already + cUpdateChunk() - 1); - req.setRawHeader("Range", rangeHeaderValue); - req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); - if (reply) reply->deleteLater(); - reply = manager.get(req); - connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64))); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError))); - connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot())); -} - -void PsUpdateDownloader::partMetaGot() { - typedef QList Pairs; - Pairs pairs = reply->rawHeaderPairs(); - for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { - if (QString::fromUtf8(i->first).toLower() == "content-range") { - QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second)); - if (m.hasMatch()) { - { - QMutexLocker lock(&mutex); - full = m.captured(1).toInt(); - } - emit App::app()->updateDownloading(already, full); - } - } - } -} - -int32 PsUpdateDownloader::ready() { - QMutexLocker lock(&mutex); - return already; -} - -int32 PsUpdateDownloader::size() { - QMutexLocker lock(&mutex); - return full; -} - -void PsUpdateDownloader::partFinished(qint64 got, qint64 total) { - if (!reply) return; - - QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - if (statusCode.isValid()) { - int status = statusCode.toInt(); - if (status != 200 && status != 206 && status != 416) { - LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status)); - return fatalFail(); - } - } - - if (!already && !full) { - QMutexLocker lock(&mutex); - full = total; - } - DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total)); - - if (!outputFile.isOpen()) { - if (!outputFile.open(QIODevice::Append)) { - LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName())); - return fatalFail(); - } - } - QByteArray r = reply->readAll(); - if (!r.isEmpty()) { - outputFile.write(r); - - QMutexLocker lock(&mutex); - already += r.size(); - } - if (got >= total) { - reply->deleteLater(); - reply = 0; - outputFile.close(); - unpackUpdate(); - } else { - emit App::app()->updateDownloading(already, full); - } -} - -void PsUpdateDownloader::partFailed(QNetworkReply::NetworkError e) { - if (!reply) return; - - QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - reply->deleteLater(); - reply = 0; - if (statusCode.isValid()) { - int status = statusCode.toInt(); - if (status == 416) { // Requested range not satisfiable - outputFile.close(); - unpackUpdate(); - return; - } - } - LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e)); - emit App::app()->updateFailed(); -} - -void PsUpdateDownloader::deleteDir(const QString &dir) { +void psDeleteDir(const QString &dir) { std::wstring wDir = QDir::toNativeSeparators(dir).toStdWString(); WCHAR path[4096]; memcpy(path, wDir.c_str(), (wDir.size() + 1) * sizeof(WCHAR)); @@ -1582,185 +1405,6 @@ void PsUpdateDownloader::deleteDir(const QString &dir) { int res = SHFileOperation(&file_op); } -void PsUpdateDownloader::fatalFail() { - clearAll(); - emit App::app()->updateFailed(); -} - -void PsUpdateDownloader::clearAll() { - deleteDir(cWorkingDir() + qsl("tupdates")); -} - -QString winapiErrorWrap() { - WCHAR errMsg[2048]; - DWORD errorCode = GetLastError(); - LPTSTR errorText = NULL, errorTextDefault = L"(Unknown error)"; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, 0); - if (!errorText) { - errorText = errorTextDefault; - } - StringCbPrintf(errMsg, sizeof(errMsg), L"Error code: %d, error message: %s", errorCode, errorText); - if (errorText != errorTextDefault) { - LocalFree(errorText); - } - return QString::fromWCharArray(errMsg); -} - -void PsUpdateDownloader::unpackUpdate() { - QByteArray packed; - if (!outputFile.open(QIODevice::ReadOnly)) { - LOG(("Update Error: cant read updates file!")); - return fatalFail(); - } - - const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header - - QByteArray compressed = outputFile.readAll(); - int32 compressedLen = compressed.size() - hSize; - if (compressedLen <= 0) { - LOG(("Update Error: bad compressed size: %1").arg(compressed.size())); - return fatalFail(); - } - outputFile.close(); - - QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyDirPath = cWorkingDir() + qsl("tupdates/ready"); - deleteDir(tempDirPath); - deleteDir(readyDirPath); - - { - QDir tempDir(tempDirPath), readyDir(readyDirPath); - if (tempDir.exists() || readyDir.exists()) { - LOG(("Update Error: cant clear tupdates/temp or tupdates/ready dir!")); - return fatalFail(); - } - - tempDirPath = tempDir.absolutePath(); - readyDirPath = readyDir.absolutePath(); - - uchar sha1Buffer[20]; - bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen); - if (!goodSha1) { - LOG(("Update Error: bad SHA1 hash of update file!")); - return fatalFail(); - } - - RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0); - if (!pbKey) { - LOG(("Update Error: cant read public rsa key!")); - return fatalFail(); - } - if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature - RSA_free(pbKey); - LOG(("Update Error: bad RSA signature of update file!")); - return fatalFail(); - } - RSA_free(pbKey); - - QByteArray uncompressed; - - int32 uncompressedLen; - memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen); - uncompressed.resize(uncompressedLen); - - size_t resultLen = uncompressed.size(); - SizeT srcLen = compressedLen; - int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE); - if (uncompressRes != SZ_OK) { - LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes)); - return fatalFail(); - } - - QDir().mkdir(tempDirPath); - - quint32 version; - - QBuffer buffer(&uncompressed); - buffer.open(QIODevice::ReadOnly); - QDataStream stream(&buffer); - stream.setVersion(QDataStream::Qt_5_1); - - stream >> version; - if (stream.status() != QDataStream::Ok) { - LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status())); - return fatalFail(); - } - if (version <= AppVersion) { - LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion)); - return fatalFail(); - } - - quint32 filesCount; - stream >> filesCount; - if (stream.status() != QDataStream::Ok) { - LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status())); - return fatalFail(); - } - if (!filesCount) { - LOG(("Update Error: update is empty!")); - return fatalFail(); - } - for (int32 i = 0; i < filesCount; ++i) { - QString relativeName; - quint32 fileSize; - QByteArray fileInnerData; - - stream >> relativeName >> fileSize >> fileInnerData; - if (stream.status() != QDataStream::Ok) { - LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status())); - return fatalFail(); - } - if (fileSize != fileInnerData.size()) { - LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size())); - return fatalFail(); - } - - QFile f(tempDirPath + '/' + relativeName); - if (!f.open(QIODevice::WriteOnly)) { - LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName)); - return fatalFail(); - } - if (f.write(fileInnerData) != fileSize) { - f.close(); - LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName)); - return fatalFail(); - } - f.close(); - } - - // create tdata/version file - QDir().mkdir(tempDirPath + qsl("/tdata")); - std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString(); - DWORD versionNum = DWORD(version), versionLen = DWORD(versionString.size() * sizeof(WCHAR)); - WCHAR versionStr[32]; - memcpy(versionStr, versionString.c_str(), versionLen); - - QFile fVersion(tempDirPath + qsl("/tdata/version")); - if (!fVersion.open(QIODevice::WriteOnly)) { - LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/tdata/version"))); - return fatalFail(); - } - fVersion.write((const char*)&versionNum, sizeof(DWORD)); - fVersion.write((const char*)&versionLen, sizeof(DWORD)); - fVersion.write((const char*)&versionStr[0], versionLen); - fVersion.close(); - } - - std::wstring tempDirNative = QDir::toNativeSeparators(tempDirPath).toStdWString(), readyDirNative = QDir::toNativeSeparators(readyDirPath).toStdWString(); - if (!MoveFile(tempDirNative.c_str(), readyDirNative.c_str())) { - LOG(("Update Error: cant rename temp dir '%1' to ready dir '%2'. %3").arg(QString::fromStdWString(tempDirNative)).arg(QString::fromStdWString(readyDirNative)).arg(winapiErrorWrap())); - return fatalFail(); - } - deleteDir(tempDirPath); - outputFile.remove(); - - emit App::app()->updateReady(); -} - -PsUpdateDownloader::~PsUpdateDownloader() { - delete reply; - reply = 0; -} - namespace { BOOL CALLBACK _ActivateProcess(HWND hWnd, LPARAM lParam) { uint64 &processId(*(uint64*)lParam); @@ -2134,65 +1778,6 @@ int psFixPrevious() { return 0; } -bool psCheckReadyUpdate() { - QString readyPath = cWorkingDir() + qsl("tupdates/ready"); - if (!QDir(readyPath).exists()) { - return false; - } - - // check ready version - QString versionPath = readyPath + qsl("/tdata/version"); - { - QFile fVersion(versionPath); - if (!fVersion.open(QIODevice::ReadOnly)) { - LOG(("Update Error: cant read version file '%1'").arg(versionPath)); - PsUpdateDownloader::clearAll(); - return false; - } - DWORD versionNum; - if (fVersion.read((char*)&versionNum, sizeof(DWORD)) != sizeof(DWORD)) { - LOG(("Update Error: cant read version from file '%1'").arg(versionPath)); - PsUpdateDownloader::clearAll(); - return false; - } - fVersion.close(); - if (versionNum <= AppVersion) { - LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion)); - PsUpdateDownloader::clearAll(); - return false; - } - } - - QString curUpdater = (cExeDir() + qsl("Updater.exe")); - QFileInfo updater(cWorkingDir() + qsl("tupdates/ready/Updater.exe")); - if (!updater.exists()) { - QFileInfo current(curUpdater); - if (!current.exists()) { - PsUpdateDownloader::clearAll(); - return false; - } - if (CopyFile(current.absoluteFilePath().toStdWString().c_str(), updater.absoluteFilePath().toStdWString().c_str(), TRUE) == FALSE) { - PsUpdateDownloader::clearAll(); - return false; - } - } - if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) { - DWORD errorCode = GetLastError(); - if (errorCode == ERROR_ACCESS_DENIED) { // we are in write-protected dir, like Program Files - cSetWriteProtected(true); - return true; - } else { - PsUpdateDownloader::clearAll(); - return false; - } - } - if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) { - PsUpdateDownloader::clearAll(); - return false; - } - return true; -} - void psPostprocessFile(const QString &name) { std::wstring zoneFile = QDir::toNativeSeparators(name).toStdWString() + L":Zone.Identifier"; HANDLE f = CreateFile(zoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); @@ -2474,7 +2059,7 @@ void psExecUpdater() { if (cStartInTray()) targs += qsl(" -startintray"); if (cWriteProtected()) targs += qsl(" -writeprotected \"") + cExeDir() + '"'; - QString updaterPath = cWriteProtected() ? (cWorkingDir() + qsl("tupdates/ready/Updater.exe")) : (cExeDir() + qsl("Updater.exe")); + QString updaterPath = cWriteProtected() ? (cWorkingDir() + qsl("tupdates/temp/Updater.exe")) : (cExeDir() + qsl("Updater.exe")); QString updater(QDir::toNativeSeparators(updaterPath)), wdir(QDir::toNativeSeparators(cWorkingDir())); @@ -2482,8 +2067,7 @@ void psExecUpdater() { HINSTANCE r = ShellExecute(0, cWriteProtected() ? L"runas" : 0, updater.toStdWString().c_str(), targs.toStdWString().c_str(), wdir.isEmpty() ? 0 : wdir.toStdWString().c_str(), SW_SHOWNORMAL); if (long(r) < 32) { DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3").arg(updater).arg(wdir).arg(long(r))); - QString readyPath = cWorkingDir() + qsl("tupdates/ready"); - PsUpdateDownloader::deleteDir(readyPath); + psDeleteDir(cWorkingDir() + qsl("tupdates/temp")); } } diff --git a/Telegram/SourceFiles/pspecific_wnd.h b/Telegram/SourceFiles/pspecific_wnd.h index 99ed0b8504..6e8040b239 100644 --- a/Telegram/SourceFiles/pspecific_wnd.h +++ b/Telegram/SourceFiles/pspecific_wnd.h @@ -117,55 +117,9 @@ public: void psInstallEventFilter(); ~PsApplication(); -signals: - - void updateChecking(); - void updateLatest(); - void updateDownloading(qint64 ready, qint64 total); - void updateReady(); - void updateFailed(); - }; -class PsUpdateDownloader : public QObject { - Q_OBJECT - -public: - PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update); - PsUpdateDownloader(QThread *thread, const QString &url); - - void unpackUpdate(); - - int32 ready(); - int32 size(); - - static void deleteDir(const QString &dir); - static void clearAll(); - - ~PsUpdateDownloader(); - -public slots: - - void start(); - void partMetaGot(); - void partFinished(qint64 got, qint64 total); - void partFailed(QNetworkReply::NetworkError e); - void sendRequest(); - -private: - void initOutput(); - - void fatalFail(); - - QString updateUrl; - QNetworkAccessManager manager; - QNetworkReply *reply; - int32 already, full; - QFile outputFile; - - QMutex mutex; - -}; +void psDeleteDir(const QString &dir); void psUserActionDone(); bool psIdleSupported(); @@ -196,7 +150,6 @@ void psBringToBack(QWidget *w); int psCleanup(); int psFixPrevious(); -bool psCheckReadyUpdate(); void psExecUpdater(); void psExecTelegram(); diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index e598810020..17e3c62bb4 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -37,6 +37,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "langloaderplain.h" #include "gui/filedialog.h" +#include "autoupdater.h" + #include "localstorage.h" Slider::Slider(QWidget *parent, const style::slider &st, int32 count, int32 sel) : QWidget(parent), @@ -1172,7 +1174,7 @@ void SettingsInner::onCheckNow() { } void SettingsInner::onRestartNow() { - psCheckReadyUpdate(); + checkReadyUpdate(); if (_updatingState == UpdatingReady) { cSetRestartingUpdate(true); } else { diff --git a/Telegram/SourceFiles/sysbuttons.cpp b/Telegram/SourceFiles/sysbuttons.cpp index 6095ba43d0..29e11ac697 100644 --- a/Telegram/SourceFiles/sysbuttons.cpp +++ b/Telegram/SourceFiles/sysbuttons.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "passcodewidget.h" #include "window.h" #include "application.h" +#include "autoupdater.h" SysBtn::SysBtn(QWidget *parent, const style::sysButton &st, const QString &text) : Button(parent), _st(st), a_color(_st.color->c), _overLevel(0), _text(text) { @@ -142,7 +143,7 @@ UpdateBtn::UpdateBtn(QWidget *parent, Window *window, const QString &text) : Sys } void UpdateBtn::onClick() { - psCheckReadyUpdate(); + checkReadyUpdate(); if (App::app()->updatingState() == Application::UpdatingReady) { cSetRestartingUpdate(true); } else { diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index fa46bff5b1..f392e92807 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -180,6 +180,10 @@ true true + + true + true + true true @@ -442,6 +446,10 @@ true true + + true + true + true true @@ -729,6 +737,10 @@ true true + + true + true + true true @@ -964,6 +976,7 @@ + @@ -1383,6 +1396,20 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\mpg123-1.22.1\ports\MSVC++" "-I.\..\..\Libraries\mpg123-1.22.1\src\libmpg123" "-I.\..\..\Libraries\faad2-2.7\include" "-I.\..\..\Libraries\faad2-2.7\common\mp4ff" "-I.\..\..\Libraries\ffmpeg-2.6.3" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" "-fstdafx.h" "-f../../SourceFiles/boxes/stickersetbox.h" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing autoupdater.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/autoupdater.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\ffmpeg-2.6.3" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing autoupdater.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/autoupdater.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\ffmpeg-2.6.3" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing autoupdater.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/autoupdater.h" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\mpg123-1.22.1\ports\MSVC++" "-I.\..\..\Libraries\mpg123-1.22.1\src\libmpg123" "-I.\..\..\Libraries\faad2-2.7\include" "-I.\..\..\Libraries\faad2-2.7\common\mp4ff" "-I.\..\..\Libraries\ffmpeg-2.6.3" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + Moc%27ing animation.h... diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 74e10dc9c1..6694d680f8 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -891,6 +891,18 @@ boxes + + Source Files + + + Generated Files\Deploy + + + Generated Files\Debug + + + Generated Files\Release + @@ -1183,6 +1195,9 @@ boxes + + Source Files + diff --git a/Telegram/_openal_patch.diff b/Telegram/_openal_patch.diff new file mode 100644 index 0000000000..951e0e1c0d --- /dev/null +++ b/Telegram/_openal_patch.diff @@ -0,0 +1,35 @@ +diff --git a/Alc/backends/mmdevapi.c b/Alc/backends/mmdevapi.c +index cfd12d8..8a6f9fb 100644 +--- a/Alc/backends/mmdevapi.c ++++ b/Alc/backends/mmdevapi.c +@@ -1719,7 +1719,7 @@ static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory* UNUSED(self)) + + static ALCboolean ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory* UNUSED(self), ALCbackend_Type type) + { +- if(type == ALCbackend_Playback || type == ALCbackend_Capture) ++ if(type == ALCbackend_Playback/* || type == ALCbackend_Capture*/) + return ALC_TRUE; + return ALC_FALSE; + } +diff --git a/Alc/backends/winmm.c b/Alc/backends/winmm.c +index 03805ab..77212c2 100644 +--- a/Alc/backends/winmm.c ++++ b/Alc/backends/winmm.c +@@ -220,7 +220,7 @@ FORCE_ALIGN static int ALCwinmmPlayback_mixerProc(void *arg) + SetRTPriority(); + althrd_setname(althrd_current(), MIXER_THREAD_NAME); + +- while(GetMessage(&msg, NULL, 0, 0)) ++ if (!self->killNow) while (GetMessage(&msg, NULL, 0, 0)) + { + if(msg.message != WOM_DONE) + continue; +@@ -505,7 +505,7 @@ static int ALCwinmmCapture_captureProc(void *arg) + + althrd_setname(althrd_current(), RECORD_THREAD_NAME); + +- while(GetMessage(&msg, NULL, 0, 0)) ++ if (!self->killNow) while(GetMessage(&msg, NULL, 0, 0)) + { + if(msg.message != WIM_DATA) + continue; diff --git a/Telegram/_openal_patch/Alc/ALc.c b/Telegram/_openal_patch/Alc/ALc.c deleted file mode 100644 index 34104fb26a..0000000000 --- a/Telegram/_openal_patch/Alc/ALc.c +++ /dev/null @@ -1,3771 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2007 by authors. - * 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; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "alMain.h" -#include "alSource.h" -#include "alListener.h" -#include "alThunk.h" -#include "alSource.h" -#include "alBuffer.h" -#include "alAuxEffectSlot.h" -#include "alError.h" -#include "alMidi.h" -#include "bs2b.h" -#include "alu.h" - -#include "compat.h" -#include "threads.h" -#include "alstring.h" - -#include "backends/base.h" -#include "midi/base.h" - - -/************************************************ - * Backends - ************************************************/ -struct BackendInfo { - const char *name; - ALCbackendFactory* (*getFactory)(void); - ALCboolean (*Init)(BackendFuncs*); - void (*Deinit)(void); - void (*Probe)(enum DevProbe); - BackendFuncs Funcs; -}; - -#define EmptyFuncs { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } -static struct BackendInfo BackendList[] = { -#ifdef HAVE_PULSEAUDIO - { "pulse", ALCpulseBackendFactory_getFactory, NULL, NULL, NULL, EmptyFuncs }, -#endif -#ifdef HAVE_ALSA - { "alsa", ALCalsaBackendFactory_getFactory, NULL, NULL, NULL, EmptyFuncs }, -#endif -#ifdef HAVE_COREAUDIO - { "core", NULL, alc_ca_init, alc_ca_deinit, alc_ca_probe, EmptyFuncs }, -#endif -#ifdef HAVE_OSS - { "oss", ALCossBackendFactory_getFactory, NULL, NULL, NULL, EmptyFuncs }, -#endif -#ifdef HAVE_SOLARIS - { "solaris", NULL, alc_solaris_init, alc_solaris_deinit, alc_solaris_probe, EmptyFuncs }, -#endif -#ifdef HAVE_SNDIO - { "sndio", NULL, alc_sndio_init, alc_sndio_deinit, alc_sndio_probe, EmptyFuncs }, -#endif -#ifdef HAVE_QSA - { "qsa", NULL, alc_qsa_init, alc_qsa_deinit, alc_qsa_probe, EmptyFuncs }, -#endif -#ifdef HAVE_MMDEVAPI - { "mmdevapi", ALCmmdevBackendFactory_getFactory, NULL, NULL, NULL, EmptyFuncs }, -#endif -#ifdef HAVE_DSOUND - { "dsound", ALCdsoundBackendFactory_getFactory, NULL, NULL, NULL, EmptyFuncs }, -#endif -#ifdef HAVE_WINMM - { "winmm", NULL, alcWinMMInit, alcWinMMDeinit, alcWinMMProbe, EmptyFuncs }, -#endif -#ifdef HAVE_PORTAUDIO - { "port", NULL, alc_pa_init, alc_pa_deinit, alc_pa_probe, EmptyFuncs }, -#endif -#ifdef HAVE_OPENSL - { "opensl", NULL, alc_opensl_init, alc_opensl_deinit, alc_opensl_probe, EmptyFuncs }, -#endif - - { "null", ALCnullBackendFactory_getFactory, NULL, NULL, NULL, EmptyFuncs }, -#ifdef HAVE_WAVE - { "wave", ALCwaveBackendFactory_getFactory, NULL, NULL, NULL, EmptyFuncs }, -#endif - - { NULL, NULL, NULL, NULL, NULL, EmptyFuncs } -}; -#undef EmptyFuncs - -static struct BackendInfo PlaybackBackend; -static struct BackendInfo CaptureBackend; - -static bool SuspendAndProcessSupported = false; - - -/************************************************ - * Functions, enums, and errors - ************************************************/ -typedef struct ALCfunction { - const ALCchar *funcName; - ALCvoid *address; -} ALCfunction; - -typedef struct ALCenums { - const ALCchar *enumName; - ALCenum value; -} ALCenums; - -#define DECL(x) { #x, (ALCvoid*)(x) } -static const ALCfunction alcFunctions[] = { - DECL(alcCreateContext), - DECL(alcMakeContextCurrent), - DECL(alcProcessContext), - DECL(alcSuspendContext), - DECL(alcDestroyContext), - DECL(alcGetCurrentContext), - DECL(alcGetContextsDevice), - DECL(alcOpenDevice), - DECL(alcCloseDevice), - DECL(alcGetError), - DECL(alcIsExtensionPresent), - DECL(alcGetProcAddress), - DECL(alcGetEnumValue), - DECL(alcGetString), - DECL(alcGetIntegerv), - DECL(alcCaptureOpenDevice), - DECL(alcCaptureCloseDevice), - DECL(alcCaptureStart), - DECL(alcCaptureStop), - DECL(alcCaptureSamples), - - DECL(alcSetThreadContext), - DECL(alcGetThreadContext), - - DECL(alcLoopbackOpenDeviceSOFT), - DECL(alcIsRenderFormatSupportedSOFT), - DECL(alcRenderSamplesSOFT), - - DECL(alcDevicePauseSOFT), - DECL(alcDeviceResumeSOFT), - - DECL(alcGetInteger64vSOFT), - - DECL(alEnable), - DECL(alDisable), - DECL(alIsEnabled), - DECL(alGetString), - DECL(alGetBooleanv), - DECL(alGetIntegerv), - DECL(alGetFloatv), - DECL(alGetDoublev), - DECL(alGetBoolean), - DECL(alGetInteger), - DECL(alGetFloat), - DECL(alGetDouble), - DECL(alGetError), - DECL(alIsExtensionPresent), - DECL(alGetProcAddress), - DECL(alGetEnumValue), - DECL(alListenerf), - DECL(alListener3f), - DECL(alListenerfv), - DECL(alListeneri), - DECL(alListener3i), - DECL(alListeneriv), - DECL(alGetListenerf), - DECL(alGetListener3f), - DECL(alGetListenerfv), - DECL(alGetListeneri), - DECL(alGetListener3i), - DECL(alGetListeneriv), - DECL(alGenSources), - DECL(alDeleteSources), - DECL(alIsSource), - DECL(alSourcef), - DECL(alSource3f), - DECL(alSourcefv), - DECL(alSourcei), - DECL(alSource3i), - DECL(alSourceiv), - DECL(alGetSourcef), - DECL(alGetSource3f), - DECL(alGetSourcefv), - DECL(alGetSourcei), - DECL(alGetSource3i), - DECL(alGetSourceiv), - DECL(alSourcePlayv), - DECL(alSourceStopv), - DECL(alSourceRewindv), - DECL(alSourcePausev), - DECL(alSourcePlay), - DECL(alSourceStop), - DECL(alSourceRewind), - DECL(alSourcePause), - DECL(alSourceQueueBuffers), - DECL(alSourceUnqueueBuffers), - DECL(alGenBuffers), - DECL(alDeleteBuffers), - DECL(alIsBuffer), - DECL(alBufferData), - DECL(alBufferf), - DECL(alBuffer3f), - DECL(alBufferfv), - DECL(alBufferi), - DECL(alBuffer3i), - DECL(alBufferiv), - DECL(alGetBufferf), - DECL(alGetBuffer3f), - DECL(alGetBufferfv), - DECL(alGetBufferi), - DECL(alGetBuffer3i), - DECL(alGetBufferiv), - DECL(alDopplerFactor), - DECL(alDopplerVelocity), - DECL(alSpeedOfSound), - DECL(alDistanceModel), - - DECL(alGenFilters), - DECL(alDeleteFilters), - DECL(alIsFilter), - DECL(alFilteri), - DECL(alFilteriv), - DECL(alFilterf), - DECL(alFilterfv), - DECL(alGetFilteri), - DECL(alGetFilteriv), - DECL(alGetFilterf), - DECL(alGetFilterfv), - DECL(alGenEffects), - DECL(alDeleteEffects), - DECL(alIsEffect), - DECL(alEffecti), - DECL(alEffectiv), - DECL(alEffectf), - DECL(alEffectfv), - DECL(alGetEffecti), - DECL(alGetEffectiv), - DECL(alGetEffectf), - DECL(alGetEffectfv), - DECL(alGenAuxiliaryEffectSlots), - DECL(alDeleteAuxiliaryEffectSlots), - DECL(alIsAuxiliaryEffectSlot), - DECL(alAuxiliaryEffectSloti), - DECL(alAuxiliaryEffectSlotiv), - DECL(alAuxiliaryEffectSlotf), - DECL(alAuxiliaryEffectSlotfv), - DECL(alGetAuxiliaryEffectSloti), - DECL(alGetAuxiliaryEffectSlotiv), - DECL(alGetAuxiliaryEffectSlotf), - DECL(alGetAuxiliaryEffectSlotfv), - - DECL(alBufferSubDataSOFT), - - DECL(alBufferSamplesSOFT), - DECL(alBufferSubSamplesSOFT), - DECL(alGetBufferSamplesSOFT), - DECL(alIsBufferFormatSupportedSOFT), - - DECL(alDeferUpdatesSOFT), - DECL(alProcessUpdatesSOFT), - - DECL(alSourcedSOFT), - DECL(alSource3dSOFT), - DECL(alSourcedvSOFT), - DECL(alGetSourcedSOFT), - DECL(alGetSource3dSOFT), - DECL(alGetSourcedvSOFT), - DECL(alSourcei64SOFT), - DECL(alSource3i64SOFT), - DECL(alSourcei64vSOFT), - DECL(alGetSourcei64SOFT), - DECL(alGetSource3i64SOFT), - DECL(alGetSourcei64vSOFT), - - DECL(alGenSoundfontsSOFT), - DECL(alDeleteSoundfontsSOFT), - DECL(alIsSoundfontSOFT), - DECL(alGetSoundfontivSOFT), - DECL(alSoundfontPresetsSOFT), - DECL(alGenPresetsSOFT), - DECL(alDeletePresetsSOFT), - DECL(alIsPresetSOFT), - DECL(alPresetiSOFT), - DECL(alPresetivSOFT), - DECL(alGetPresetivSOFT), - DECL(alPresetFontsoundsSOFT), - DECL(alGenFontsoundsSOFT), - DECL(alDeleteFontsoundsSOFT), - DECL(alIsFontsoundSOFT), - DECL(alFontsoundiSOFT), - DECL(alFontsound2iSOFT), - DECL(alFontsoundivSOFT), - DECL(alGetFontsoundivSOFT), - DECL(alFontsoundModulatoriSOFT), - DECL(alGetFontsoundModulatorivSOFT), - DECL(alMidiSoundfontSOFT), - DECL(alMidiSoundfontvSOFT), - DECL(alMidiEventSOFT), - DECL(alMidiSysExSOFT), - DECL(alMidiPlaySOFT), - DECL(alMidiPauseSOFT), - DECL(alMidiStopSOFT), - DECL(alMidiResetSOFT), - DECL(alMidiGainSOFT), - DECL(alGetInteger64SOFT), - DECL(alGetInteger64vSOFT), - DECL(alLoadSoundfontSOFT), - - { NULL, NULL } -}; -#undef DECL - -#define DECL(x) { #x, (x) } -static const ALCenums enumeration[] = { - DECL(ALC_INVALID), - DECL(ALC_FALSE), - DECL(ALC_TRUE), - - DECL(ALC_MAJOR_VERSION), - DECL(ALC_MINOR_VERSION), - DECL(ALC_ATTRIBUTES_SIZE), - DECL(ALC_ALL_ATTRIBUTES), - DECL(ALC_DEFAULT_DEVICE_SPECIFIER), - DECL(ALC_DEVICE_SPECIFIER), - DECL(ALC_ALL_DEVICES_SPECIFIER), - DECL(ALC_DEFAULT_ALL_DEVICES_SPECIFIER), - DECL(ALC_EXTENSIONS), - DECL(ALC_FREQUENCY), - DECL(ALC_REFRESH), - DECL(ALC_SYNC), - DECL(ALC_MONO_SOURCES), - DECL(ALC_STEREO_SOURCES), - DECL(ALC_CAPTURE_DEVICE_SPECIFIER), - DECL(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER), - DECL(ALC_CAPTURE_SAMPLES), - DECL(ALC_CONNECTED), - - DECL(ALC_EFX_MAJOR_VERSION), - DECL(ALC_EFX_MINOR_VERSION), - DECL(ALC_MAX_AUXILIARY_SENDS), - - DECL(ALC_FORMAT_CHANNELS_SOFT), - DECL(ALC_FORMAT_TYPE_SOFT), - - DECL(ALC_MONO_SOFT), - DECL(ALC_STEREO_SOFT), - DECL(ALC_QUAD_SOFT), - DECL(ALC_5POINT1_SOFT), - DECL(ALC_6POINT1_SOFT), - DECL(ALC_7POINT1_SOFT), - - DECL(ALC_BYTE_SOFT), - DECL(ALC_UNSIGNED_BYTE_SOFT), - DECL(ALC_SHORT_SOFT), - DECL(ALC_UNSIGNED_SHORT_SOFT), - DECL(ALC_INT_SOFT), - DECL(ALC_UNSIGNED_INT_SOFT), - DECL(ALC_FLOAT_SOFT), - - DECL(ALC_NO_ERROR), - DECL(ALC_INVALID_DEVICE), - DECL(ALC_INVALID_CONTEXT), - DECL(ALC_INVALID_ENUM), - DECL(ALC_INVALID_VALUE), - DECL(ALC_OUT_OF_MEMORY), - - - DECL(AL_INVALID), - DECL(AL_NONE), - DECL(AL_FALSE), - DECL(AL_TRUE), - - DECL(AL_SOURCE_RELATIVE), - DECL(AL_CONE_INNER_ANGLE), - DECL(AL_CONE_OUTER_ANGLE), - DECL(AL_PITCH), - DECL(AL_POSITION), - DECL(AL_DIRECTION), - DECL(AL_VELOCITY), - DECL(AL_LOOPING), - DECL(AL_BUFFER), - DECL(AL_GAIN), - DECL(AL_MIN_GAIN), - DECL(AL_MAX_GAIN), - DECL(AL_ORIENTATION), - DECL(AL_REFERENCE_DISTANCE), - DECL(AL_ROLLOFF_FACTOR), - DECL(AL_CONE_OUTER_GAIN), - DECL(AL_MAX_DISTANCE), - DECL(AL_SEC_OFFSET), - DECL(AL_SAMPLE_OFFSET), - DECL(AL_SAMPLE_RW_OFFSETS_SOFT), - DECL(AL_BYTE_OFFSET), - DECL(AL_BYTE_RW_OFFSETS_SOFT), - DECL(AL_SOURCE_TYPE), - DECL(AL_STATIC), - DECL(AL_STREAMING), - DECL(AL_UNDETERMINED), - DECL(AL_METERS_PER_UNIT), - DECL(AL_DIRECT_CHANNELS_SOFT), - - DECL(AL_DIRECT_FILTER), - DECL(AL_AUXILIARY_SEND_FILTER), - DECL(AL_AIR_ABSORPTION_FACTOR), - DECL(AL_ROOM_ROLLOFF_FACTOR), - DECL(AL_CONE_OUTER_GAINHF), - DECL(AL_DIRECT_FILTER_GAINHF_AUTO), - DECL(AL_AUXILIARY_SEND_FILTER_GAIN_AUTO), - DECL(AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO), - - DECL(AL_SOURCE_STATE), - DECL(AL_INITIAL), - DECL(AL_PLAYING), - DECL(AL_PAUSED), - DECL(AL_STOPPED), - - DECL(AL_BUFFERS_QUEUED), - DECL(AL_BUFFERS_PROCESSED), - - DECL(AL_FORMAT_MONO8), - DECL(AL_FORMAT_MONO16), - DECL(AL_FORMAT_MONO_FLOAT32), - DECL(AL_FORMAT_MONO_DOUBLE_EXT), - DECL(AL_FORMAT_STEREO8), - DECL(AL_FORMAT_STEREO16), - DECL(AL_FORMAT_STEREO_FLOAT32), - DECL(AL_FORMAT_STEREO_DOUBLE_EXT), - DECL(AL_FORMAT_MONO_IMA4), - DECL(AL_FORMAT_STEREO_IMA4), - DECL(AL_FORMAT_MONO_MSADPCM_SOFT), - DECL(AL_FORMAT_STEREO_MSADPCM_SOFT), - DECL(AL_FORMAT_QUAD8_LOKI), - DECL(AL_FORMAT_QUAD16_LOKI), - DECL(AL_FORMAT_QUAD8), - DECL(AL_FORMAT_QUAD16), - DECL(AL_FORMAT_QUAD32), - DECL(AL_FORMAT_51CHN8), - DECL(AL_FORMAT_51CHN16), - DECL(AL_FORMAT_51CHN32), - DECL(AL_FORMAT_61CHN8), - DECL(AL_FORMAT_61CHN16), - DECL(AL_FORMAT_61CHN32), - DECL(AL_FORMAT_71CHN8), - DECL(AL_FORMAT_71CHN16), - DECL(AL_FORMAT_71CHN32), - DECL(AL_FORMAT_REAR8), - DECL(AL_FORMAT_REAR16), - DECL(AL_FORMAT_REAR32), - DECL(AL_FORMAT_MONO_MULAW), - DECL(AL_FORMAT_MONO_MULAW_EXT), - DECL(AL_FORMAT_STEREO_MULAW), - DECL(AL_FORMAT_STEREO_MULAW_EXT), - DECL(AL_FORMAT_QUAD_MULAW), - DECL(AL_FORMAT_51CHN_MULAW), - DECL(AL_FORMAT_61CHN_MULAW), - DECL(AL_FORMAT_71CHN_MULAW), - DECL(AL_FORMAT_REAR_MULAW), - DECL(AL_FORMAT_MONO_ALAW_EXT), - DECL(AL_FORMAT_STEREO_ALAW_EXT), - - DECL(AL_MONO8_SOFT), - DECL(AL_MONO16_SOFT), - DECL(AL_MONO32F_SOFT), - DECL(AL_STEREO8_SOFT), - DECL(AL_STEREO16_SOFT), - DECL(AL_STEREO32F_SOFT), - DECL(AL_QUAD8_SOFT), - DECL(AL_QUAD16_SOFT), - DECL(AL_QUAD32F_SOFT), - DECL(AL_REAR8_SOFT), - DECL(AL_REAR16_SOFT), - DECL(AL_REAR32F_SOFT), - DECL(AL_5POINT1_8_SOFT), - DECL(AL_5POINT1_16_SOFT), - DECL(AL_5POINT1_32F_SOFT), - DECL(AL_6POINT1_8_SOFT), - DECL(AL_6POINT1_16_SOFT), - DECL(AL_6POINT1_32F_SOFT), - DECL(AL_7POINT1_8_SOFT), - DECL(AL_7POINT1_16_SOFT), - DECL(AL_7POINT1_32F_SOFT), - - DECL(AL_MONO_SOFT), - DECL(AL_STEREO_SOFT), - DECL(AL_QUAD_SOFT), - DECL(AL_REAR_SOFT), - DECL(AL_5POINT1_SOFT), - DECL(AL_6POINT1_SOFT), - DECL(AL_7POINT1_SOFT), - - DECL(AL_BYTE_SOFT), - DECL(AL_UNSIGNED_BYTE_SOFT), - DECL(AL_SHORT_SOFT), - DECL(AL_UNSIGNED_SHORT_SOFT), - DECL(AL_INT_SOFT), - DECL(AL_UNSIGNED_INT_SOFT), - DECL(AL_FLOAT_SOFT), - DECL(AL_DOUBLE_SOFT), - DECL(AL_BYTE3_SOFT), - DECL(AL_UNSIGNED_BYTE3_SOFT), - - DECL(AL_FREQUENCY), - DECL(AL_BITS), - DECL(AL_CHANNELS), - DECL(AL_SIZE), - DECL(AL_INTERNAL_FORMAT_SOFT), - DECL(AL_BYTE_LENGTH_SOFT), - DECL(AL_SAMPLE_LENGTH_SOFT), - DECL(AL_SEC_LENGTH_SOFT), - DECL(AL_UNPACK_BLOCK_ALIGNMENT_SOFT), - DECL(AL_PACK_BLOCK_ALIGNMENT_SOFT), - - DECL(AL_UNUSED), - DECL(AL_PENDING), - DECL(AL_PROCESSED), - - DECL(AL_NO_ERROR), - DECL(AL_INVALID_NAME), - DECL(AL_INVALID_ENUM), - DECL(AL_INVALID_VALUE), - DECL(AL_INVALID_OPERATION), - DECL(AL_OUT_OF_MEMORY), - - DECL(AL_VENDOR), - DECL(AL_VERSION), - DECL(AL_RENDERER), - DECL(AL_EXTENSIONS), - - DECL(AL_DOPPLER_FACTOR), - DECL(AL_DOPPLER_VELOCITY), - DECL(AL_DISTANCE_MODEL), - DECL(AL_SPEED_OF_SOUND), - DECL(AL_SOURCE_DISTANCE_MODEL), - DECL(AL_DEFERRED_UPDATES_SOFT), - - DECL(AL_INVERSE_DISTANCE), - DECL(AL_INVERSE_DISTANCE_CLAMPED), - DECL(AL_LINEAR_DISTANCE), - DECL(AL_LINEAR_DISTANCE_CLAMPED), - DECL(AL_EXPONENT_DISTANCE), - DECL(AL_EXPONENT_DISTANCE_CLAMPED), - - DECL(AL_FILTER_TYPE), - DECL(AL_FILTER_NULL), - DECL(AL_FILTER_LOWPASS), - DECL(AL_FILTER_HIGHPASS), - DECL(AL_FILTER_BANDPASS), - - DECL(AL_LOWPASS_GAIN), - DECL(AL_LOWPASS_GAINHF), - - DECL(AL_HIGHPASS_GAIN), - DECL(AL_HIGHPASS_GAINLF), - - DECL(AL_BANDPASS_GAIN), - DECL(AL_BANDPASS_GAINHF), - DECL(AL_BANDPASS_GAINLF), - - DECL(AL_EFFECT_TYPE), - DECL(AL_EFFECT_NULL), - DECL(AL_EFFECT_REVERB), - DECL(AL_EFFECT_EAXREVERB), - DECL(AL_EFFECT_CHORUS), - DECL(AL_EFFECT_DISTORTION), - DECL(AL_EFFECT_ECHO), - DECL(AL_EFFECT_FLANGER), -#if 0 - DECL(AL_EFFECT_FREQUENCY_SHIFTER), - DECL(AL_EFFECT_VOCAL_MORPHER), - DECL(AL_EFFECT_PITCH_SHIFTER), -#endif - DECL(AL_EFFECT_RING_MODULATOR), -#if 0 - DECL(AL_EFFECT_AUTOWAH), -#endif - DECL(AL_EFFECT_COMPRESSOR), - DECL(AL_EFFECT_EQUALIZER), - DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT), - DECL(AL_EFFECT_DEDICATED_DIALOGUE), - - DECL(AL_EAXREVERB_DENSITY), - DECL(AL_EAXREVERB_DIFFUSION), - DECL(AL_EAXREVERB_GAIN), - DECL(AL_EAXREVERB_GAINHF), - DECL(AL_EAXREVERB_GAINLF), - DECL(AL_EAXREVERB_DECAY_TIME), - DECL(AL_EAXREVERB_DECAY_HFRATIO), - DECL(AL_EAXREVERB_DECAY_LFRATIO), - DECL(AL_EAXREVERB_REFLECTIONS_GAIN), - DECL(AL_EAXREVERB_REFLECTIONS_DELAY), - DECL(AL_EAXREVERB_REFLECTIONS_PAN), - DECL(AL_EAXREVERB_LATE_REVERB_GAIN), - DECL(AL_EAXREVERB_LATE_REVERB_DELAY), - DECL(AL_EAXREVERB_LATE_REVERB_PAN), - DECL(AL_EAXREVERB_ECHO_TIME), - DECL(AL_EAXREVERB_ECHO_DEPTH), - DECL(AL_EAXREVERB_MODULATION_TIME), - DECL(AL_EAXREVERB_MODULATION_DEPTH), - DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF), - DECL(AL_EAXREVERB_HFREFERENCE), - DECL(AL_EAXREVERB_LFREFERENCE), - DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR), - DECL(AL_EAXREVERB_DECAY_HFLIMIT), - - DECL(AL_REVERB_DENSITY), - DECL(AL_REVERB_DIFFUSION), - DECL(AL_REVERB_GAIN), - DECL(AL_REVERB_GAINHF), - DECL(AL_REVERB_DECAY_TIME), - DECL(AL_REVERB_DECAY_HFRATIO), - DECL(AL_REVERB_REFLECTIONS_GAIN), - DECL(AL_REVERB_REFLECTIONS_DELAY), - DECL(AL_REVERB_LATE_REVERB_GAIN), - DECL(AL_REVERB_LATE_REVERB_DELAY), - DECL(AL_REVERB_AIR_ABSORPTION_GAINHF), - DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR), - DECL(AL_REVERB_DECAY_HFLIMIT), - - DECL(AL_CHORUS_WAVEFORM), - DECL(AL_CHORUS_PHASE), - DECL(AL_CHORUS_RATE), - DECL(AL_CHORUS_DEPTH), - DECL(AL_CHORUS_FEEDBACK), - DECL(AL_CHORUS_DELAY), - - DECL(AL_DISTORTION_EDGE), - DECL(AL_DISTORTION_GAIN), - DECL(AL_DISTORTION_LOWPASS_CUTOFF), - DECL(AL_DISTORTION_EQCENTER), - DECL(AL_DISTORTION_EQBANDWIDTH), - - DECL(AL_ECHO_DELAY), - DECL(AL_ECHO_LRDELAY), - DECL(AL_ECHO_DAMPING), - DECL(AL_ECHO_FEEDBACK), - DECL(AL_ECHO_SPREAD), - - DECL(AL_FLANGER_WAVEFORM), - DECL(AL_FLANGER_PHASE), - DECL(AL_FLANGER_RATE), - DECL(AL_FLANGER_DEPTH), - DECL(AL_FLANGER_FEEDBACK), - DECL(AL_FLANGER_DELAY), - - DECL(AL_RING_MODULATOR_FREQUENCY), - DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF), - DECL(AL_RING_MODULATOR_WAVEFORM), - -#if 0 - DECL(AL_AUTOWAH_ATTACK_TIME), - DECL(AL_AUTOWAH_PEAK_GAIN), - DECL(AL_AUTOWAH_RELEASE_TIME), - DECL(AL_AUTOWAH_RESONANCE), -#endif - - DECL(AL_COMPRESSOR_ONOFF), - - DECL(AL_EQUALIZER_LOW_GAIN), - DECL(AL_EQUALIZER_LOW_CUTOFF), - DECL(AL_EQUALIZER_MID1_GAIN), - DECL(AL_EQUALIZER_MID1_CENTER), - DECL(AL_EQUALIZER_MID1_WIDTH), - DECL(AL_EQUALIZER_MID2_GAIN), - DECL(AL_EQUALIZER_MID2_CENTER), - DECL(AL_EQUALIZER_MID2_WIDTH), - DECL(AL_EQUALIZER_HIGH_GAIN), - DECL(AL_EQUALIZER_HIGH_CUTOFF), - - DECL(AL_DEDICATED_GAIN), - - { NULL, (ALCenum)0 } -}; -#undef DECL - -static const ALCchar alcNoError[] = "No Error"; -static const ALCchar alcErrInvalidDevice[] = "Invalid Device"; -static const ALCchar alcErrInvalidContext[] = "Invalid Context"; -static const ALCchar alcErrInvalidEnum[] = "Invalid Enum"; -static const ALCchar alcErrInvalidValue[] = "Invalid Value"; -static const ALCchar alcErrOutOfMemory[] = "Out of Memory"; - - -/************************************************ - * Global variables - ************************************************/ - -/* Enumerated device names */ -static const ALCchar alcDefaultName[] = "OpenAL Soft\0"; - -static al_string alcAllDevicesList; -static al_string alcCaptureDeviceList; - -/* Default is always the first in the list */ -static ALCchar *alcDefaultAllDevicesSpecifier; -static ALCchar *alcCaptureDefaultDeviceSpecifier; - -/* Default context extensions */ -static const ALchar alExtList[] = - "AL_EXT_ALAW AL_EXT_DOUBLE AL_EXT_EXPONENT_DISTANCE AL_EXT_FLOAT32 " - "AL_EXT_IMA4 AL_EXT_LINEAR_DISTANCE AL_EXT_MCFORMATS AL_EXT_MULAW " - "AL_EXT_MULAW_MCFORMATS AL_EXT_OFFSET AL_EXT_source_distance_model " - "AL_LOKI_quadriphonic AL_SOFT_block_alignment AL_SOFT_buffer_samples " - "AL_SOFT_buffer_sub_data AL_SOFT_deferred_updates AL_SOFT_direct_channels " - "AL_SOFT_loop_points AL_SOFT_MSADPCM AL_SOFT_source_latency " - "AL_SOFT_source_length"; - -static ATOMIC(ALCenum) LastNullDeviceError = ATOMIC_INIT_STATIC(ALC_NO_ERROR); - -/* Thread-local current context */ -static altss_t LocalContext; -/* Process-wide current context */ -static ATOMIC(ALCcontext*) GlobalContext = ATOMIC_INIT_STATIC(NULL); - -/* Mixing thread piority level */ -ALint RTPrioLevel; - -FILE *LogFile; -#ifdef _DEBUG -enum LogLevel LogLevel = LogWarning; -#else -enum LogLevel LogLevel = LogError; -#endif - -/* Flag to trap ALC device errors */ -static ALCboolean TrapALCError = ALC_FALSE; - -/* One-time configuration init control */ -static alonce_flag alc_config_once = AL_ONCE_FLAG_INIT; - -/* Default effect that applies to sources that don't have an effect on send 0 */ -static ALeffect DefaultEffect; - - -/************************************************ - * ALC information - ************************************************/ -static const ALCchar alcNoDeviceExtList[] = - "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " - "ALC_EXT_thread_local_context ALC_SOFT_loopback"; -static const ALCchar alcExtensionList[] = - "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " - "ALC_EXT_DEDICATED ALC_EXT_disconnect ALC_EXT_EFX " - "ALC_EXT_thread_local_context ALC_SOFTX_device_clock ALC_SOFTX_HRTF " - "ALC_SOFT_loopback ALC_SOFTX_midi_interface ALC_SOFT_pause_device"; -static const ALCint alcMajorVersion = 1; -static const ALCint alcMinorVersion = 1; - -static const ALCint alcEFXMajorVersion = 1; -static const ALCint alcEFXMinorVersion = 0; - - -/************************************************ - * Device lists - ************************************************/ -static ATOMIC(ALCdevice*) DeviceList = ATOMIC_INIT_STATIC(NULL); - -static almtx_t ListLock; -static inline void LockLists(void) -{ - int lockret = almtx_lock(&ListLock); - assert(lockret == althrd_success); -} -static inline void UnlockLists(void) -{ - int unlockret = almtx_unlock(&ListLock); - assert(unlockret == althrd_success); -} - -/************************************************ - * Library initialization - ************************************************/ -#if defined(_WIN32) -static void alc_init(void); -static void alc_deinit(void); -static void alc_deinit_safe(void); - -#ifndef AL_LIBTYPE_STATIC -BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD reason, LPVOID lpReserved) -{ - switch(reason) - { - case DLL_PROCESS_ATTACH: - /* Pin the DLL so we won't get unloaded until the process terminates */ - GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, - (WCHAR*)hModule, &hModule); - alc_init(); - break; - - case DLL_THREAD_DETACH: - break; - - case DLL_PROCESS_DETACH: - if(!lpReserved) - alc_deinit(); - else - alc_deinit_safe(); - break; - } - return TRUE; -} -#elif defined(_MSC_VER) -#pragma section(".CRT$XCU",read) -static void alc_constructor(void); -static void alc_destructor(void); -__declspec(allocate(".CRT$XCU")) void (__cdecl* alc_constructor_)(void) = alc_constructor; - -static void alc_constructor(void) -{ - atexit(alc_destructor); - alc_init(); -} - -static void alc_destructor(void) -{ - alc_deinit(); -} -#elif defined(HAVE_GCC_DESTRUCTOR) -static void alc_init(void) __attribute__((constructor)); -static void alc_deinit(void) __attribute__((destructor)); -#else -#error "No static initialization available on this platform!" -#endif - -#elif defined(HAVE_GCC_DESTRUCTOR) - -static void alc_init(void) __attribute__((constructor)); -static void alc_deinit(void) __attribute__((destructor)); - -#else -#error "No global initialization available on this platform!" -#endif - -static void ReleaseThreadCtx(void *ptr); -static void alc_init(void) -{ - const char *str; - int ret; - - LogFile = stderr; - - AL_STRING_INIT(alcAllDevicesList); - AL_STRING_INIT(alcCaptureDeviceList); - - str = getenv("__ALSOFT_HALF_ANGLE_CONES"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1)) - ConeScale *= 0.5f; - - str = getenv("__ALSOFT_REVERSE_Z"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1)) - ZScale *= -1.0f; - - ret = altss_create(&LocalContext, ReleaseThreadCtx); - assert(ret == althrd_success); - - ret = almtx_init(&ListLock, almtx_recursive); - assert(ret == althrd_success); - - ThunkInit(); -} - -static void alc_initconfig(void) -{ - const char *devs, *str; - ALuint capfilter; - float valf; - int i, n; - - str = getenv("ALSOFT_LOGLEVEL"); - if(str) - { - long lvl = strtol(str, NULL, 0); - if(lvl >= NoLog && lvl <= LogRef) - LogLevel = lvl; - } - - str = getenv("ALSOFT_LOGFILE"); - if(str && str[0]) - { - FILE *logfile = al_fopen(str, "wt"); - if(logfile) LogFile = logfile; - else ERR("Failed to open log file '%s'\n", str); - } - - { - char buf[1024] = ""; - int len = snprintf(buf, sizeof(buf), "%s", BackendList[0].name); - for(i = 1;BackendList[i].name;i++) - len += snprintf(buf+len, sizeof(buf)-len, ", %s", BackendList[i].name); - TRACE("Supported backends: %s\n", buf); - } - ReadALConfig(); - - capfilter = 0; -#if defined(HAVE_SSE4_1) - capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE4_1; -#elif defined(HAVE_SSE2) - capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2; -#elif defined(HAVE_SSE) - capfilter |= CPU_CAP_SSE; -#endif -#ifdef HAVE_NEON - capfilter |= CPU_CAP_NEON; -#endif - if(ConfigValueStr(NULL, "disable-cpu-exts", &str)) - { - if(strcasecmp(str, "all") == 0) - capfilter = 0; - else - { - size_t len; - const char *next = str; - - do { - str = next; - while(isspace(str[0])) - str++; - next = strchr(str, ','); - - if(!str[0] || str[0] == ',') - continue; - - len = (next ? ((size_t)(next-str)) : strlen(str)); - while(len > 0 && isspace(str[len-1])) - len--; - if(len == 3 && strncasecmp(str, "sse", len) == 0) - capfilter &= ~CPU_CAP_SSE; - else if(len == 4 && strncasecmp(str, "sse2", len) == 0) - capfilter &= ~CPU_CAP_SSE2; - else if(len == 6 && strncasecmp(str, "sse4.1", len) == 0) - capfilter &= ~CPU_CAP_SSE4_1; - else if(len == 4 && strncasecmp(str, "neon", len) == 0) - capfilter &= ~CPU_CAP_NEON; - else - WARN("Invalid CPU extension \"%s\"\n", str); - } while(next++); - } - } - FillCPUCaps(capfilter); - -#ifdef _WIN32 - RTPrioLevel = 1; -#else - RTPrioLevel = 0; -#endif - ConfigValueInt(NULL, "rt-prio", &RTPrioLevel); - - if(ConfigValueStr(NULL, "resampler", &str)) - { - if(strcasecmp(str, "point") == 0 || strcasecmp(str, "none") == 0) - DefaultResampler = PointResampler; - else if(strcasecmp(str, "linear") == 0) - DefaultResampler = LinearResampler; - else if(strcasecmp(str, "cubic") == 0) - DefaultResampler = CubicResampler; - else - { - char *end; - - n = strtol(str, &end, 0); - if(*end == '\0' && (n == PointResampler || n == LinearResampler || n == CubicResampler)) - DefaultResampler = n; - else - WARN("Invalid resampler: %s\n", str); - } - } - - str = getenv("ALSOFT_TRAP_ERROR"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1)) - { - TrapALError = AL_TRUE; - TrapALCError = AL_TRUE; - } - else - { - str = getenv("ALSOFT_TRAP_AL_ERROR"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1)) - TrapALError = AL_TRUE; - TrapALError = GetConfigValueBool(NULL, "trap-al-error", TrapALError); - - str = getenv("ALSOFT_TRAP_ALC_ERROR"); - if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1)) - TrapALCError = ALC_TRUE; - TrapALCError = GetConfigValueBool(NULL, "trap-alc-error", TrapALCError); - } - - if(ConfigValueFloat("reverb", "boost", &valf)) - ReverbBoost *= powf(10.0f, valf / 20.0f); - - EmulateEAXReverb = GetConfigValueBool("reverb", "emulate-eax", AL_FALSE); - - if(((devs=getenv("ALSOFT_DRIVERS")) && devs[0]) || - ConfigValueStr(NULL, "drivers", &devs)) - { - int n; - size_t len; - const char *next = devs; - int endlist, delitem; - - i = 0; - do { - devs = next; - while(isspace(devs[0])) - devs++; - next = strchr(devs, ','); - - delitem = (devs[0] == '-'); - if(devs[0] == '-') devs++; - - if(!devs[0] || devs[0] == ',') - { - endlist = 0; - continue; - } - endlist = 1; - - len = (next ? ((size_t)(next-devs)) : strlen(devs)); - while(len > 0 && isspace(devs[len-1])) - len--; - for(n = i;BackendList[n].name;n++) - { - if(len == strlen(BackendList[n].name) && - strncmp(BackendList[n].name, devs, len) == 0) - { - if(delitem) - { - do { - BackendList[n] = BackendList[n+1]; - ++n; - } while(BackendList[n].name); - } - else - { - struct BackendInfo Bkp = BackendList[n]; - while(n > i) - { - BackendList[n] = BackendList[n-1]; - --n; - } - BackendList[n] = Bkp; - - i++; - } - break; - } - } - } while(next++); - - if(endlist) - { - BackendList[i].name = NULL; - BackendList[i].getFactory = NULL; - BackendList[i].Init = NULL; - BackendList[i].Deinit = NULL; - BackendList[i].Probe = NULL; - } - } - - for(i = 0;(BackendList[i].Init || BackendList[i].getFactory) && (!PlaybackBackend.name || !CaptureBackend.name);i++) - { - if(BackendList[i].getFactory) - { - ALCbackendFactory *factory = BackendList[i].getFactory(); - if(!V0(factory,init)()) - { - WARN("Failed to initialize backend \"%s\"\n", BackendList[i].name); - continue; - } - - TRACE("Initialized backend \"%s\"\n", BackendList[i].name); - if(!PlaybackBackend.name && V(factory,querySupport)(ALCbackend_Playback)) - { - PlaybackBackend = BackendList[i]; - TRACE("Added \"%s\" for playback\n", PlaybackBackend.name); - } - if(!CaptureBackend.name && V(factory,querySupport)(ALCbackend_Capture)) - { - CaptureBackend = BackendList[i]; - TRACE("Added \"%s\" for capture\n", CaptureBackend.name); - } - - continue; - } - - if(!BackendList[i].Init(&BackendList[i].Funcs)) - { - WARN("Failed to initialize backend \"%s\"\n", BackendList[i].name); - continue; - } - - TRACE("Initialized backend \"%s\"\n", BackendList[i].name); - if(BackendList[i].Funcs.OpenPlayback && !PlaybackBackend.name) - { - PlaybackBackend = BackendList[i]; - TRACE("Added \"%s\" for playback\n", PlaybackBackend.name); - } - if(BackendList[i].Funcs.OpenCapture && !CaptureBackend.name) - { - CaptureBackend = BackendList[i]; - TRACE("Added \"%s\" for capture\n", CaptureBackend.name); - } - } - { - ALCbackendFactory *factory = ALCloopbackFactory_getFactory(); - V0(factory,init)(); - } - - if(ConfigValueStr(NULL, "excludefx", &str)) - { - size_t len; - const char *next = str; - - do { - str = next; - next = strchr(str, ','); - - if(!str[0] || next == str) - continue; - - len = (next ? ((size_t)(next-str)) : strlen(str)); - for(n = 0;EffectList[n].name;n++) - { - if(len == strlen(EffectList[n].name) && - strncmp(EffectList[n].name, str, len) == 0) - DisabledEffects[EffectList[n].type] = AL_TRUE; - } - } while(next++); - } - - InitEffectFactoryMap(); - - InitEffect(&DefaultEffect); - str = getenv("ALSOFT_DEFAULT_REVERB"); - if((str && str[0]) || ConfigValueStr(NULL, "default-reverb", &str)) - LoadReverbPreset(str, &DefaultEffect); -} -#define DO_INITCONFIG() alcall_once(&alc_config_once, alc_initconfig) - - -/************************************************ - * Library deinitialization - ************************************************/ -static void alc_cleanup(void) -{ - ALCdevice *dev; - - AL_STRING_DEINIT(alcAllDevicesList); - AL_STRING_DEINIT(alcCaptureDeviceList); - - free(alcDefaultAllDevicesSpecifier); - alcDefaultAllDevicesSpecifier = NULL; - free(alcCaptureDefaultDeviceSpecifier); - alcCaptureDefaultDeviceSpecifier = NULL; - - if((dev=ATOMIC_EXCHANGE(ALCdevice*, &DeviceList, NULL)) != NULL) - { - ALCuint num = 0; - do { - num++; - } while((dev=dev->next) != NULL); - ERR("%u device%s not closed\n", num, (num>1)?"s":""); - } - - DeinitEffectFactoryMap(); -} - -static void alc_deinit_safe(void) -{ - alc_cleanup(); - - FreeHrtfs(); - FreeALConfig(); - - ThunkExit(); - almtx_destroy(&ListLock); - altss_delete(LocalContext); - - if(LogFile != stderr) - fclose(LogFile); - LogFile = NULL; -} - -static void alc_deinit(void) -{ - int i; - - alc_cleanup(); - - memset(&PlaybackBackend, 0, sizeof(PlaybackBackend)); - memset(&CaptureBackend, 0, sizeof(CaptureBackend)); - - for(i = 0;BackendList[i].Deinit || BackendList[i].getFactory;i++) - { - if(!BackendList[i].getFactory) - BackendList[i].Deinit(); - else - { - ALCbackendFactory *factory = BackendList[i].getFactory(); - V0(factory,deinit)(); - } - } - { - ALCbackendFactory *factory = ALCloopbackFactory_getFactory(); - V0(factory,deinit)(); - } - - alc_deinit_safe(); -} - - -/************************************************ - * Device enumeration - ************************************************/ -static void ProbeDevices(al_string *list, enum DevProbe type) -{ - DO_INITCONFIG(); - - LockLists(); - al_string_clear(list); - - if(type == ALL_DEVICE_PROBE && (PlaybackBackend.Probe || PlaybackBackend.getFactory)) - { - if(!PlaybackBackend.getFactory) - PlaybackBackend.Probe(type); - else - { - ALCbackendFactory *factory = PlaybackBackend.getFactory(); - V(factory,probe)(type); - } - } - else if(type == CAPTURE_DEVICE_PROBE && (CaptureBackend.Probe || CaptureBackend.getFactory)) - { - if(!CaptureBackend.getFactory) - CaptureBackend.Probe(type); - else - { - ALCbackendFactory *factory = CaptureBackend.getFactory(); - V(factory,probe)(type); - } - } - UnlockLists(); -} -static void ProbeAllDevicesList(void) -{ ProbeDevices(&alcAllDevicesList, ALL_DEVICE_PROBE); } -static void ProbeCaptureDeviceList(void) -{ ProbeDevices(&alcCaptureDeviceList, CAPTURE_DEVICE_PROBE); } - -static void AppendDevice(const ALCchar *name, al_string *devnames) -{ - size_t len = strlen(name); - if(len > 0) - { - al_string_append_range(devnames, name, name+len); - al_string_append_char(devnames, '\0'); - } -} -void AppendAllDevicesList(const ALCchar *name) -{ AppendDevice(name, &alcAllDevicesList); } -void AppendCaptureDeviceList(const ALCchar *name) -{ AppendDevice(name, &alcCaptureDeviceList); } - - -/************************************************ - * Device format information - ************************************************/ -const ALCchar *DevFmtTypeString(enum DevFmtType type) -{ - switch(type) - { - case DevFmtByte: return "Signed Byte"; - case DevFmtUByte: return "Unsigned Byte"; - case DevFmtShort: return "Signed Short"; - case DevFmtUShort: return "Unsigned Short"; - case DevFmtInt: return "Signed Int"; - case DevFmtUInt: return "Unsigned Int"; - case DevFmtFloat: return "Float"; - } - return "(unknown type)"; -} -const ALCchar *DevFmtChannelsString(enum DevFmtChannels chans) -{ - switch(chans) - { - case DevFmtMono: return "Mono"; - case DevFmtStereo: return "Stereo"; - case DevFmtQuad: return "Quadraphonic"; - case DevFmtX51: return "5.1 Surround"; - case DevFmtX51Side: return "5.1 Side"; - case DevFmtX61: return "6.1 Surround"; - case DevFmtX71: return "7.1 Surround"; - } - return "(unknown channels)"; -} - -extern inline ALuint FrameSizeFromDevFmt(enum DevFmtChannels chans, enum DevFmtType type); -ALuint BytesFromDevFmt(enum DevFmtType type) -{ - switch(type) - { - case DevFmtByte: return sizeof(ALbyte); - case DevFmtUByte: return sizeof(ALubyte); - case DevFmtShort: return sizeof(ALshort); - case DevFmtUShort: return sizeof(ALushort); - case DevFmtInt: return sizeof(ALint); - case DevFmtUInt: return sizeof(ALuint); - case DevFmtFloat: return sizeof(ALfloat); - } - return 0; -} -ALuint ChannelsFromDevFmt(enum DevFmtChannels chans) -{ - switch(chans) - { - case DevFmtMono: return 1; - case DevFmtStereo: return 2; - case DevFmtQuad: return 4; - case DevFmtX51: return 6; - case DevFmtX51Side: return 6; - case DevFmtX61: return 7; - case DevFmtX71: return 8; - } - return 0; -} - -DECL_CONST static ALboolean DecomposeDevFormat(ALenum format, - enum DevFmtChannels *chans, enum DevFmtType *type) -{ - static const struct { - ALenum format; - enum DevFmtChannels channels; - enum DevFmtType type; - } list[] = { - { AL_FORMAT_MONO8, DevFmtMono, DevFmtUByte }, - { AL_FORMAT_MONO16, DevFmtMono, DevFmtShort }, - { AL_FORMAT_MONO_FLOAT32, DevFmtMono, DevFmtFloat }, - - { AL_FORMAT_STEREO8, DevFmtStereo, DevFmtUByte }, - { AL_FORMAT_STEREO16, DevFmtStereo, DevFmtShort }, - { AL_FORMAT_STEREO_FLOAT32, DevFmtStereo, DevFmtFloat }, - - { AL_FORMAT_QUAD8, DevFmtQuad, DevFmtUByte }, - { AL_FORMAT_QUAD16, DevFmtQuad, DevFmtShort }, - { AL_FORMAT_QUAD32, DevFmtQuad, DevFmtFloat }, - - { AL_FORMAT_51CHN8, DevFmtX51, DevFmtUByte }, - { AL_FORMAT_51CHN16, DevFmtX51, DevFmtShort }, - { AL_FORMAT_51CHN32, DevFmtX51, DevFmtFloat }, - - { AL_FORMAT_61CHN8, DevFmtX61, DevFmtUByte }, - { AL_FORMAT_61CHN16, DevFmtX61, DevFmtShort }, - { AL_FORMAT_61CHN32, DevFmtX61, DevFmtFloat }, - - { AL_FORMAT_71CHN8, DevFmtX71, DevFmtUByte }, - { AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort }, - { AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat }, - }; - ALuint i; - - for(i = 0;i < COUNTOF(list);i++) - { - if(list[i].format == format) - { - *chans = list[i].channels; - *type = list[i].type; - return AL_TRUE; - } - } - - return AL_FALSE; -} - -DECL_CONST static ALCboolean IsValidALCType(ALCenum type) -{ - switch(type) - { - case ALC_BYTE_SOFT: - case ALC_UNSIGNED_BYTE_SOFT: - case ALC_SHORT_SOFT: - case ALC_UNSIGNED_SHORT_SOFT: - case ALC_INT_SOFT: - case ALC_UNSIGNED_INT_SOFT: - case ALC_FLOAT_SOFT: - return ALC_TRUE; - } - return ALC_FALSE; -} - -DECL_CONST static ALCboolean IsValidALCChannels(ALCenum channels) -{ - switch(channels) - { - case ALC_MONO_SOFT: - case ALC_STEREO_SOFT: - case ALC_QUAD_SOFT: - case ALC_5POINT1_SOFT: - case ALC_6POINT1_SOFT: - case ALC_7POINT1_SOFT: - return ALC_TRUE; - } - return ALC_FALSE; -} - - -/************************************************ - * Miscellaneous ALC helpers - ************************************************/ -extern inline void LockContext(ALCcontext *context); -extern inline void UnlockContext(ALCcontext *context); - -ALint64 ALCdevice_GetLatencyDefault(ALCdevice *UNUSED(device)) -{ - return 0; -} - -ALint64 ALCdevice_GetLatency(ALCdevice *device) -{ - return V0(device->Backend,getLatency)(); -} - -void ALCdevice_Lock(ALCdevice *device) -{ - V0(device->Backend,lock)(); -} - -void ALCdevice_Unlock(ALCdevice *device) -{ - V0(device->Backend,unlock)(); -} - - -/* SetDefaultWFXChannelOrder - * - * Sets the default channel order used by WaveFormatEx. - */ -void SetDefaultWFXChannelOrder(ALCdevice *device) -{ - ALuint i; - - for(i = 0;i < MaxChannels;i++) - device->ChannelOffsets[i] = INVALID_OFFSET; - - switch(device->FmtChans) - { - case DevFmtMono: device->ChannelOffsets[FrontCenter] = 0; - break; - case DevFmtStereo: device->ChannelOffsets[FrontLeft] = 0; - device->ChannelOffsets[FrontRight] = 1; - break; - case DevFmtQuad: device->ChannelOffsets[FrontLeft] = 0; - device->ChannelOffsets[FrontRight] = 1; - device->ChannelOffsets[BackLeft] = 2; - device->ChannelOffsets[BackRight] = 3; - break; - case DevFmtX51: device->ChannelOffsets[FrontLeft] = 0; - device->ChannelOffsets[FrontRight] = 1; - device->ChannelOffsets[FrontCenter] = 2; - device->ChannelOffsets[LFE] = 3; - device->ChannelOffsets[BackLeft] = 4; - device->ChannelOffsets[BackRight] = 5; - break; - case DevFmtX51Side: device->ChannelOffsets[FrontLeft] = 0; - device->ChannelOffsets[FrontRight] = 1; - device->ChannelOffsets[FrontCenter] = 2; - device->ChannelOffsets[LFE] = 3; - device->ChannelOffsets[SideLeft] = 4; - device->ChannelOffsets[SideRight] = 5; - break; - case DevFmtX61: device->ChannelOffsets[FrontLeft] = 0; - device->ChannelOffsets[FrontRight] = 1; - device->ChannelOffsets[FrontCenter] = 2; - device->ChannelOffsets[LFE] = 3; - device->ChannelOffsets[BackCenter] = 4; - device->ChannelOffsets[SideLeft] = 5; - device->ChannelOffsets[SideRight] = 6; - break; - case DevFmtX71: device->ChannelOffsets[FrontLeft] = 0; - device->ChannelOffsets[FrontRight] = 1; - device->ChannelOffsets[FrontCenter] = 2; - device->ChannelOffsets[LFE] = 3; - device->ChannelOffsets[BackLeft] = 4; - device->ChannelOffsets[BackRight] = 5; - device->ChannelOffsets[SideLeft] = 6; - device->ChannelOffsets[SideRight] = 7; - break; - } -} - -/* SetDefaultChannelOrder - * - * Sets the default channel order used by most non-WaveFormatEx-based APIs. - */ -void SetDefaultChannelOrder(ALCdevice *device) -{ - ALuint i; - - for(i = 0;i < MaxChannels;i++) - device->ChannelOffsets[i] = INVALID_OFFSET; - - switch(device->FmtChans) - { - case DevFmtX51: device->ChannelOffsets[FrontLeft] = 0; - device->ChannelOffsets[FrontRight] = 1; - device->ChannelOffsets[BackLeft] = 2; - device->ChannelOffsets[BackRight] = 3; - device->ChannelOffsets[FrontCenter] = 4; - device->ChannelOffsets[LFE] = 5; - return; - case DevFmtX71: device->ChannelOffsets[FrontLeft] = 0; - device->ChannelOffsets[FrontRight] = 1; - device->ChannelOffsets[BackLeft] = 2; - device->ChannelOffsets[BackRight] = 3; - device->ChannelOffsets[FrontCenter] = 4; - device->ChannelOffsets[LFE] = 5; - device->ChannelOffsets[SideLeft] = 6; - device->ChannelOffsets[SideRight] = 7; - return; - - /* Same as WFX order */ - case DevFmtMono: - case DevFmtStereo: - case DevFmtQuad: - case DevFmtX51Side: - case DevFmtX61: - break; - } - SetDefaultWFXChannelOrder(device); -} - - -/* alcSetError - * - * Stores the latest ALC device error - */ -static void alcSetError(ALCdevice *device, ALCenum errorCode) -{ - if(TrapALCError) - { -#ifdef _WIN32 - /* DebugBreak() will cause an exception if there is no debugger */ - if(IsDebuggerPresent()) - DebugBreak(); -#elif defined(SIGTRAP) - raise(SIGTRAP); -#endif - } - - if(device) - ATOMIC_STORE(&device->LastError, errorCode); - else - ATOMIC_STORE(&LastNullDeviceError, errorCode); -} - - -/* UpdateClockBase - * - * Updates the device's base clock time with however many samples have been - * done. This is used so frequency changes on the device don't cause the time - * to jump forward or back. - */ -static inline void UpdateClockBase(ALCdevice *device) -{ - device->ClockBase += device->SamplesDone * DEVICE_CLOCK_RES / device->Frequency; - device->SamplesDone = 0; -} - -/* UpdateDeviceParams - * - * Updates device parameters according to the attribute list (caller is - * responsible for holding the list lock). - */ -static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList) -{ - ALCcontext *context; - enum DevFmtChannels oldChans; - enum DevFmtType oldType; - ALCuint oldFreq; - FPUCtl oldMode; - - // Check for attributes - if(device->Type == Loopback) - { - enum { - GotFreq = 1<<0, - GotChans = 1<<1, - GotType = 1<<2, - GotAll = GotFreq|GotChans|GotType - }; - ALCuint freq, numMono, numStereo, numSends, flags; - enum DevFmtChannels schans; - enum DevFmtType stype; - ALCuint attrIdx = 0; - ALCint gotFmt = 0; - - if(!attrList) - { - WARN("Missing attributes for loopback device\n"); - return ALC_INVALID_VALUE; - } - - numMono = device->NumMonoSources; - numStereo = device->NumStereoSources; - numSends = device->NumAuxSends; - schans = device->FmtChans; - stype = device->FmtType; - freq = device->Frequency; - flags = device->Flags; - - while(attrList[attrIdx]) - { - if(attrList[attrIdx] == ALC_FORMAT_CHANNELS_SOFT) - { - ALCint val = attrList[attrIdx + 1]; - if(!IsValidALCChannels(val) || !ChannelsFromDevFmt(val)) - return ALC_INVALID_VALUE; - schans = val; - gotFmt |= GotChans; - } - - if(attrList[attrIdx] == ALC_FORMAT_TYPE_SOFT) - { - ALCint val = attrList[attrIdx + 1]; - if(!IsValidALCType(val) || !BytesFromDevFmt(val)) - return ALC_INVALID_VALUE; - stype = val; - gotFmt |= GotType; - } - - if(attrList[attrIdx] == ALC_FREQUENCY) - { - freq = attrList[attrIdx + 1]; - if(freq < MIN_OUTPUT_RATE) - return ALC_INVALID_VALUE; - gotFmt |= GotFreq; - } - - if(attrList[attrIdx] == ALC_STEREO_SOURCES) - { - numStereo = attrList[attrIdx + 1]; - if(numStereo > device->MaxNoOfSources) - numStereo = device->MaxNoOfSources; - - numMono = device->MaxNoOfSources - numStereo; - } - - if(attrList[attrIdx] == ALC_MAX_AUXILIARY_SENDS) - numSends = attrList[attrIdx + 1]; - - if(attrList[attrIdx] == ALC_HRTF_SOFT) - { - if(attrList[attrIdx + 1] != ALC_FALSE) - flags |= DEVICE_HRTF_REQUEST; - else - flags &= ~DEVICE_HRTF_REQUEST; - } - - attrIdx += 2; - } - - if(gotFmt != GotAll) - { - WARN("Missing format for loopback device\n"); - return ALC_INVALID_VALUE; - } - - ConfigValueUInt(NULL, "sends", &numSends); - numSends = minu(MAX_SENDS, numSends); - - if((device->Flags&DEVICE_RUNNING)) - V0(device->Backend,stop)(); - device->Flags = (flags & ~DEVICE_RUNNING); - - UpdateClockBase(device); - - device->Frequency = freq; - device->FmtChans = schans; - device->FmtType = stype; - device->NumMonoSources = numMono; - device->NumStereoSources = numStereo; - device->NumAuxSends = numSends; - } - else if(attrList && attrList[0]) - { - ALCuint freq, numMono, numStereo, numSends; - ALCuint attrIdx = 0; - - /* If a context is already running on the device, stop playback so the - * device attributes can be updated. */ - if((device->Flags&DEVICE_RUNNING)) - V0(device->Backend,stop)(); - device->Flags &= ~DEVICE_RUNNING; - - freq = device->Frequency; - numMono = device->NumMonoSources; - numStereo = device->NumStereoSources; - numSends = device->NumAuxSends; - - while(attrList[attrIdx]) - { - if(attrList[attrIdx] == ALC_FREQUENCY) - { - freq = attrList[attrIdx + 1]; - device->Flags |= DEVICE_FREQUENCY_REQUEST; - } - - if(attrList[attrIdx] == ALC_STEREO_SOURCES) - { - numStereo = attrList[attrIdx + 1]; - if(numStereo > device->MaxNoOfSources) - numStereo = device->MaxNoOfSources; - - numMono = device->MaxNoOfSources - numStereo; - } - - if(attrList[attrIdx] == ALC_MAX_AUXILIARY_SENDS) - numSends = attrList[attrIdx + 1]; - - if(attrList[attrIdx] == ALC_HRTF_SOFT) - { - if(attrList[attrIdx + 1] != ALC_FALSE) - device->Flags |= DEVICE_HRTF_REQUEST; - else - device->Flags &= ~DEVICE_HRTF_REQUEST; - } - - attrIdx += 2; - } - - ConfigValueUInt(NULL, "frequency", &freq); - freq = maxu(freq, MIN_OUTPUT_RATE); - - ConfigValueUInt(NULL, "sends", &numSends); - numSends = minu(MAX_SENDS, numSends); - - UpdateClockBase(device); - - device->UpdateSize = (ALuint64)device->UpdateSize * freq / - device->Frequency; - /* SSE and Neon do best with the update size being a multiple of 4 */ - if((CPUCapFlags&(CPU_CAP_SSE|CPU_CAP_NEON)) != 0) - device->UpdateSize = (device->UpdateSize+3)&~3; - - device->Frequency = freq; - device->NumMonoSources = numMono; - device->NumStereoSources = numStereo; - device->NumAuxSends = numSends; - } - - if((device->Flags&DEVICE_RUNNING)) - return ALC_NO_ERROR; - - UpdateClockBase(device); - - if(device->Type != Loopback) - { - bool usehrtf = !!(device->Flags&DEVICE_HRTF_REQUEST); - if(GetConfigValueBool(NULL, "hrtf", usehrtf)) - device->Flags |= DEVICE_HRTF_REQUEST; - else - device->Flags &= ~DEVICE_HRTF_REQUEST; - } - if((device->Flags&DEVICE_HRTF_REQUEST)) - { - enum DevFmtChannels chans = device->FmtChans; - ALCuint freq = device->Frequency; - if(FindHrtfFormat(&chans, &freq)) - { - if(device->Type != Loopback) - { - device->Frequency = freq; - device->FmtChans = chans; - device->Flags |= DEVICE_CHANNELS_REQUEST | - DEVICE_FREQUENCY_REQUEST; - } - else if(device->Frequency != freq || device->FmtChans != chans) - { - ERR("Requested format not HRTF compatible: %s, %uhz\n", - DevFmtChannelsString(device->FmtChans), device->Frequency); - device->Flags &= ~DEVICE_HRTF_REQUEST; - } - } - } - - oldFreq = device->Frequency; - oldChans = device->FmtChans; - oldType = device->FmtType; - - TRACE("Pre-reset: %s%s, %s%s, %s%uhz, %u update size x%d\n", - (device->Flags&DEVICE_CHANNELS_REQUEST)?"*":"", - DevFmtChannelsString(device->FmtChans), - (device->Flags&DEVICE_SAMPLE_TYPE_REQUEST)?"*":"", - DevFmtTypeString(device->FmtType), - (device->Flags&DEVICE_FREQUENCY_REQUEST)?"*":"", - device->Frequency, - device->UpdateSize, device->NumUpdates); - - if(V0(device->Backend,reset)() == ALC_FALSE) - return ALC_INVALID_DEVICE; - - if(device->FmtChans != oldChans && (device->Flags&DEVICE_CHANNELS_REQUEST)) - { - ERR("Failed to set %s, got %s instead\n", DevFmtChannelsString(oldChans), - DevFmtChannelsString(device->FmtChans)); - device->Flags &= ~DEVICE_CHANNELS_REQUEST; - } - if(device->FmtType != oldType && (device->Flags&DEVICE_SAMPLE_TYPE_REQUEST)) - { - ERR("Failed to set %s, got %s instead\n", DevFmtTypeString(oldType), - DevFmtTypeString(device->FmtType)); - device->Flags &= ~DEVICE_SAMPLE_TYPE_REQUEST; - } - if(device->Frequency != oldFreq && (device->Flags&DEVICE_FREQUENCY_REQUEST)) - { - ERR("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency); - device->Flags &= ~DEVICE_FREQUENCY_REQUEST; - } - - TRACE("Post-reset: %s, %s, %uhz, %u update size x%d\n", - DevFmtChannelsString(device->FmtChans), - DevFmtTypeString(device->FmtType), device->Frequency, - device->UpdateSize, device->NumUpdates); - - aluInitPanning(device); - - V(device->Synth,update)(device); - - device->Hrtf = NULL; - if((device->Flags&DEVICE_HRTF_REQUEST)) - { - device->Hrtf = GetHrtf(device->FmtChans, device->Frequency); - if(!device->Hrtf) - device->Flags &= ~DEVICE_HRTF_REQUEST; - } - TRACE("HRTF %s\n", device->Hrtf?"enabled":"disabled"); - - if(!device->Hrtf && device->Bs2bLevel > 0 && device->Bs2bLevel <= 6) - { - if(!device->Bs2b) - { - device->Bs2b = calloc(1, sizeof(*device->Bs2b)); - bs2b_clear(device->Bs2b); - } - bs2b_set_srate(device->Bs2b, device->Frequency); - bs2b_set_level(device->Bs2b, device->Bs2bLevel); - TRACE("BS2B level %d\n", device->Bs2bLevel); - } - else - { - free(device->Bs2b); - device->Bs2b = NULL; - TRACE("BS2B disabled\n"); - } - - device->Flags &= ~DEVICE_WIDE_STEREO; - if(device->Type != Loopback && !device->Hrtf && GetConfigValueBool(NULL, "wide-stereo", AL_FALSE)) - device->Flags |= DEVICE_WIDE_STEREO; - - if(!device->Hrtf && (device->UpdateSize&3)) - { - if((CPUCapFlags&CPU_CAP_SSE)) - WARN("SSE performs best with multiple of 4 update sizes (%u)\n", device->UpdateSize); - if((CPUCapFlags&CPU_CAP_NEON)) - WARN("NEON performs best with multiple of 4 update sizes (%u)\n", device->UpdateSize); - } - - SetMixerFPUMode(&oldMode); - ALCdevice_Lock(device); - context = ATOMIC_LOAD(&device->ContextList); - while(context) - { - ALsizei pos; - - ATOMIC_STORE(&context->UpdateSources, AL_FALSE); - LockUIntMapRead(&context->EffectSlotMap); - for(pos = 0;pos < context->EffectSlotMap.size;pos++) - { - ALeffectslot *slot = context->EffectSlotMap.array[pos].value; - - if(V(slot->EffectState,deviceUpdate)(device) == AL_FALSE) - { - UnlockUIntMapRead(&context->EffectSlotMap); - ALCdevice_Unlock(device); - RestoreFPUMode(&oldMode); - return ALC_INVALID_DEVICE; - } - ATOMIC_STORE(&slot->NeedsUpdate, AL_FALSE); - V(slot->EffectState,update)(device, slot); - } - UnlockUIntMapRead(&context->EffectSlotMap); - - LockUIntMapRead(&context->SourceMap); - for(pos = 0;pos < context->SourceMap.size;pos++) - { - ALsource *source = context->SourceMap.array[pos].value; - ALuint s = device->NumAuxSends; - while(s < MAX_SENDS) - { - if(source->Send[s].Slot) - DecrementRef(&source->Send[s].Slot->ref); - source->Send[s].Slot = NULL; - source->Send[s].Gain = 1.0f; - source->Send[s].GainHF = 1.0f; - s++; - } - ATOMIC_STORE(&source->NeedsUpdate, AL_TRUE); - } - UnlockUIntMapRead(&context->SourceMap); - - for(pos = 0;pos < context->VoiceCount;pos++) - { - ALvoice *voice = &context->Voices[pos]; - ALsource *source = voice->Source; - ALuint s = device->NumAuxSends; - - while(s < MAX_SENDS) - { - voice->Send[s].Moving = AL_FALSE; - voice->Send[s].Counter = 0; - s++; - } - - if(source) - { - ATOMIC_STORE(&source->NeedsUpdate, AL_FALSE); - voice->Update(voice, source, context); - } - } - - context = context->next; - } - if(device->DefaultSlot) - { - ALeffectslot *slot = device->DefaultSlot; - - if(V(slot->EffectState,deviceUpdate)(device) == AL_FALSE) - { - ALCdevice_Unlock(device); - RestoreFPUMode(&oldMode); - return ALC_INVALID_DEVICE; - } - ATOMIC_STORE(&slot->NeedsUpdate, AL_FALSE); - V(slot->EffectState,update)(device, slot); - } - ALCdevice_Unlock(device); - RestoreFPUMode(&oldMode); - - if(!(device->Flags&DEVICE_PAUSED)) - { - if(V0(device->Backend,start)() == ALC_FALSE) - return ALC_INVALID_DEVICE; - device->Flags |= DEVICE_RUNNING; - } - - return ALC_NO_ERROR; -} - -/* FreeDevice - * - * Frees the device structure, and destroys any objects the app failed to - * delete. Called once there's no more references on the device. - */ -static ALCvoid FreeDevice(ALCdevice *device) -{ - TRACE("%p\n", device); - - V0(device->Backend,close)(); - DELETE_OBJ(device->Backend); - device->Backend = NULL; - - DELETE_OBJ(device->Synth); - device->Synth = NULL; - - if(device->DefaultSlot) - { - ALeffectState *state = device->DefaultSlot->EffectState; - device->DefaultSlot = NULL; - DELETE_OBJ(state); - } - - if(device->DefaultSfont) - ALsoundfont_deleteSoundfont(device->DefaultSfont, device); - device->DefaultSfont = NULL; - - if(device->BufferMap.size > 0) - { - WARN("(%p) Deleting %d Buffer(s)\n", device, device->BufferMap.size); - ReleaseALBuffers(device); - } - ResetUIntMap(&device->BufferMap); - - if(device->EffectMap.size > 0) - { - WARN("(%p) Deleting %d Effect(s)\n", device, device->EffectMap.size); - ReleaseALEffects(device); - } - ResetUIntMap(&device->EffectMap); - - if(device->FilterMap.size > 0) - { - WARN("(%p) Deleting %d Filter(s)\n", device, device->FilterMap.size); - ReleaseALFilters(device); - } - ResetUIntMap(&device->FilterMap); - - if(device->SfontMap.size > 0) - { - WARN("(%p) Deleting %d Soundfont(s)\n", device, device->SfontMap.size); - ReleaseALSoundfonts(device); - } - ResetUIntMap(&device->SfontMap); - - if(device->PresetMap.size > 0) - { - WARN("(%p) Deleting %d Preset(s)\n", device, device->PresetMap.size); - ReleaseALPresets(device); - } - ResetUIntMap(&device->PresetMap); - - if(device->FontsoundMap.size > 0) - { - WARN("(%p) Deleting %d Fontsound(s)\n", device, device->FontsoundMap.size); - ReleaseALFontsounds(device); - } - ResetUIntMap(&device->FontsoundMap); - - free(device->Bs2b); - device->Bs2b = NULL; - - AL_STRING_DEINIT(device->DeviceName); - - al_free(device); -} - - -void ALCdevice_IncRef(ALCdevice *device) -{ - uint ref; - ref = IncrementRef(&device->ref); - TRACEREF("%p increasing refcount to %u\n", device, ref); -} - -void ALCdevice_DecRef(ALCdevice *device) -{ - uint ref; - ref = DecrementRef(&device->ref); - TRACEREF("%p decreasing refcount to %u\n", device, ref); - if(ref == 0) FreeDevice(device); -} - -/* VerifyDevice - * - * Checks if the device handle is valid, and increments its ref count if so. - */ -static ALCdevice *VerifyDevice(ALCdevice *device) -{ - ALCdevice *tmpDevice; - - if(!device) - return NULL; - - LockLists(); - tmpDevice = ATOMIC_LOAD(&DeviceList); - while(tmpDevice && tmpDevice != device) - tmpDevice = tmpDevice->next; - - if(tmpDevice) - ALCdevice_IncRef(tmpDevice); - UnlockLists(); - return tmpDevice; -} - - -/* InitContext - * - * Initializes context fields - */ -static ALvoid InitContext(ALCcontext *Context) -{ - ALint i, j; - - //Initialise listener - Context->Listener->Gain = 1.0f; - Context->Listener->MetersPerUnit = 1.0f; - Context->Listener->Position[0] = 0.0f; - Context->Listener->Position[1] = 0.0f; - Context->Listener->Position[2] = 0.0f; - Context->Listener->Velocity[0] = 0.0f; - Context->Listener->Velocity[1] = 0.0f; - Context->Listener->Velocity[2] = 0.0f; - Context->Listener->Forward[0] = 0.0f; - Context->Listener->Forward[1] = 0.0f; - Context->Listener->Forward[2] = -1.0f; - Context->Listener->Up[0] = 0.0f; - Context->Listener->Up[1] = 1.0f; - Context->Listener->Up[2] = 0.0f; - for(i = 0;i < 4;i++) - { - for(j = 0;j < 4;j++) - Context->Listener->Params.Matrix[i][j] = ((i==j) ? 1.0f : 0.0f); - } - for(i = 0;i < 3;i++) - Context->Listener->Params.Velocity[i] = 0.0f; - - //Validate Context - ATOMIC_INIT(&Context->LastError, AL_NO_ERROR); - ATOMIC_INIT(&Context->UpdateSources, AL_FALSE); - InitUIntMap(&Context->SourceMap, Context->Device->MaxNoOfSources); - InitUIntMap(&Context->EffectSlotMap, Context->Device->AuxiliaryEffectSlotMax); - - //Set globals - Context->DistanceModel = DefaultDistanceModel; - Context->SourceDistanceModel = AL_FALSE; - Context->DopplerFactor = 1.0f; - Context->DopplerVelocity = 1.0f; - Context->SpeedOfSound = SPEEDOFSOUNDMETRESPERSEC; - Context->DeferUpdates = AL_FALSE; - - Context->ExtensionList = alExtList; -} - - -/* FreeContext - * - * Cleans up the context, and destroys any remaining objects the app failed to - * delete. Called once there's no more references on the context. - */ -static void FreeContext(ALCcontext *context) -{ - TRACE("%p\n", context); - - if(context->SourceMap.size > 0) - { - WARN("(%p) Deleting %d Source(s)\n", context, context->SourceMap.size); - ReleaseALSources(context); - } - ResetUIntMap(&context->SourceMap); - - if(context->EffectSlotMap.size > 0) - { - WARN("(%p) Deleting %d AuxiliaryEffectSlot(s)\n", context, context->EffectSlotMap.size); - ReleaseALAuxiliaryEffectSlots(context); - } - ResetUIntMap(&context->EffectSlotMap); - - al_free(context->Voices); - context->Voices = NULL; - context->VoiceCount = 0; - context->MaxVoices = 0; - - VECTOR_DEINIT(context->ActiveAuxSlots); - - ALCdevice_DecRef(context->Device); - context->Device = NULL; - - //Invalidate context - memset(context, 0, sizeof(ALCcontext)); - al_free(context); -} - -/* ReleaseContext - * - * Removes the context reference from the given device and removes it from - * being current on the running thread or globally. - */ -static void ReleaseContext(ALCcontext *context, ALCdevice *device) -{ - ALCcontext *nextctx; - ALCcontext *origctx; - - if(altss_get(LocalContext) == context) - { - WARN("%p released while current on thread\n", context); - altss_set(LocalContext, NULL); - ALCcontext_DecRef(context); - } - - origctx = context; - if(ATOMIC_COMPARE_EXCHANGE_STRONG(ALCcontext*, &GlobalContext, &origctx, NULL)) - ALCcontext_DecRef(context); - - ALCdevice_Lock(device); - origctx = context; - nextctx = context->next; - if(!ATOMIC_COMPARE_EXCHANGE_STRONG(ALCcontext*, &device->ContextList, &origctx, nextctx)) - { - ALCcontext *list; - do { - list = origctx; - origctx = context; - } while(!COMPARE_EXCHANGE(&list->next, &origctx, nextctx)); - } - ALCdevice_Unlock(device); - - ALCcontext_DecRef(context); -} - -void ALCcontext_IncRef(ALCcontext *context) -{ - uint ref; - ref = IncrementRef(&context->ref); - TRACEREF("%p increasing refcount to %u\n", context, ref); -} - -void ALCcontext_DecRef(ALCcontext *context) -{ - uint ref; - ref = DecrementRef(&context->ref); - TRACEREF("%p decreasing refcount to %u\n", context, ref); - if(ref == 0) FreeContext(context); -} - -static void ReleaseThreadCtx(void *ptr) -{ - WARN("%p current for thread being destroyed\n", ptr); - ALCcontext_DecRef(ptr); -} - -/* VerifyContext - * - * Checks that the given context is valid, and increments its reference count. - */ -static ALCcontext *VerifyContext(ALCcontext *context) -{ - ALCdevice *dev; - - LockLists(); - dev = ATOMIC_LOAD(&DeviceList); - while(dev) - { - ALCcontext *ctx = ATOMIC_LOAD(&dev->ContextList); - while(ctx) - { - if(ctx == context) - { - ALCcontext_IncRef(ctx); - UnlockLists(); - return ctx; - } - ctx = ctx->next; - } - dev = dev->next; - } - UnlockLists(); - - return NULL; -} - - -/* GetContextRef - * - * Returns the currently active context for this thread, and adds a reference - * without locking it. - */ -ALCcontext *GetContextRef(void) -{ - ALCcontext *context; - - context = altss_get(LocalContext); - if(context) - ALCcontext_IncRef(context); - else - { - LockLists(); - context = ATOMIC_LOAD(&GlobalContext); - if(context) - ALCcontext_IncRef(context); - UnlockLists(); - } - - return context; -} - - -/************************************************ - * Standard ALC functions - ************************************************/ - -/* alcGetError - * - * Return last ALC generated error code for the given device -*/ -ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) -{ - ALCenum errorCode; - - if(VerifyDevice(device)) - { - errorCode = ATOMIC_EXCHANGE(ALCenum, &device->LastError, ALC_NO_ERROR); - ALCdevice_DecRef(device); - } - else - errorCode = ATOMIC_EXCHANGE(ALCenum, &LastNullDeviceError, ALC_NO_ERROR); - - return errorCode; -} - - -/* alcSuspendContext - * - * Not functional - */ -ALC_API ALCvoid ALC_APIENTRY alcSuspendContext(ALCcontext *context) -{ - if (!SuspendAndProcessSupported) return; - - ALCdevice *Device; - - LockLists(); - /* alcGetContextsDevice sets an error for invalid contexts */ - Device = alcGetContextsDevice(context); - if (Device && (Device->Flags & DEVICE_RUNNING)) - { - V0(Device->Backend, stop)(); - Device->Flags &= ~DEVICE_RUNNING; - } - UnlockLists(); -} - -/* alcProcessContext - * - * Not functional - */ -ALC_API ALCvoid ALC_APIENTRY alcProcessContext(ALCcontext *context) -{ - if (!SuspendAndProcessSupported) return; - - ALCdevice *Device; - - LockLists(); - /* alcGetContextsDevice sets an error for invalid contexts */ - Device = alcGetContextsDevice(context); - if (Device && !(Device->Flags & DEVICE_RUNNING)) - { - V0(Device->Backend, start)(); - Device->Flags |= DEVICE_RUNNING; - } - UnlockLists(); -} - - -/* alcGetString - * - * Returns information about the device, and error strings - */ -ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) -{ - const ALCchar *value = NULL; - - switch(param) - { - case ALC_NO_ERROR: - value = alcNoError; - break; - - case ALC_INVALID_ENUM: - value = alcErrInvalidEnum; - break; - - case ALC_INVALID_VALUE: - value = alcErrInvalidValue; - break; - - case ALC_INVALID_DEVICE: - value = alcErrInvalidDevice; - break; - - case ALC_INVALID_CONTEXT: - value = alcErrInvalidContext; - break; - - case ALC_OUT_OF_MEMORY: - value = alcErrOutOfMemory; - break; - - case ALC_DEVICE_SPECIFIER: - value = alcDefaultName; - break; - - case ALC_ALL_DEVICES_SPECIFIER: - if(VerifyDevice(Device)) - { - value = al_string_get_cstr(Device->DeviceName); - ALCdevice_DecRef(Device); - } - else - { - ProbeAllDevicesList(); - value = al_string_get_cstr(alcAllDevicesList); - } - break; - - case ALC_CAPTURE_DEVICE_SPECIFIER: - if(VerifyDevice(Device)) - { - value = al_string_get_cstr(Device->DeviceName); - ALCdevice_DecRef(Device); - } - else - { - ProbeCaptureDeviceList(); - value = al_string_get_cstr(alcCaptureDeviceList); - } - break; - - /* Default devices are always first in the list */ - case ALC_DEFAULT_DEVICE_SPECIFIER: - value = alcDefaultName; - break; - - case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: - if(al_string_empty(alcAllDevicesList)) - ProbeAllDevicesList(); - - Device = VerifyDevice(Device); - - free(alcDefaultAllDevicesSpecifier); - alcDefaultAllDevicesSpecifier = strdup(al_string_get_cstr(alcAllDevicesList)); - if(!alcDefaultAllDevicesSpecifier) - alcSetError(Device, ALC_OUT_OF_MEMORY); - - value = alcDefaultAllDevicesSpecifier; - if(Device) ALCdevice_DecRef(Device); - break; - - case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: - if(al_string_empty(alcCaptureDeviceList)) - ProbeCaptureDeviceList(); - - Device = VerifyDevice(Device); - - free(alcCaptureDefaultDeviceSpecifier); - alcCaptureDefaultDeviceSpecifier = strdup(al_string_get_cstr(alcCaptureDeviceList)); - if(!alcCaptureDefaultDeviceSpecifier) - alcSetError(Device, ALC_OUT_OF_MEMORY); - - value = alcCaptureDefaultDeviceSpecifier; - if(Device) ALCdevice_DecRef(Device); - break; - - case ALC_EXTENSIONS: - if(!VerifyDevice(Device)) - value = alcNoDeviceExtList; - else - { - value = alcExtensionList; - ALCdevice_DecRef(Device); - } - break; - - default: - Device = VerifyDevice(Device); - alcSetError(Device, ALC_INVALID_ENUM); - if(Device) ALCdevice_DecRef(Device); - break; - } - - return value; -} - - -static ALCsizei GetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) -{ - ALCsizei i; - - if(size <= 0 || values == NULL) - { - alcSetError(device, ALC_INVALID_VALUE); - return 0; - } - - if(!device) - { - switch(param) - { - case ALC_MAJOR_VERSION: - values[0] = alcMajorVersion; - return 1; - case ALC_MINOR_VERSION: - values[0] = alcMinorVersion; - return 1; - - case ALC_ATTRIBUTES_SIZE: - case ALC_ALL_ATTRIBUTES: - case ALC_FREQUENCY: - case ALC_REFRESH: - case ALC_SYNC: - case ALC_MONO_SOURCES: - case ALC_STEREO_SOURCES: - case ALC_CAPTURE_SAMPLES: - case ALC_FORMAT_CHANNELS_SOFT: - case ALC_FORMAT_TYPE_SOFT: - alcSetError(NULL, ALC_INVALID_DEVICE); - return 0; - - default: - alcSetError(NULL, ALC_INVALID_ENUM); - return 0; - } - return 0; - } - - if(device->Type == Capture) - { - switch(param) - { - case ALC_CAPTURE_SAMPLES: - ALCdevice_Lock(device); - values[0] = V0(device->Backend,availableSamples)(); - ALCdevice_Unlock(device); - return 1; - - case ALC_CONNECTED: - values[0] = device->Connected; - return 1; - - default: - alcSetError(device, ALC_INVALID_ENUM); - return 0; - } - return 0; - } - - /* render device */ - switch(param) - { - case ALC_MAJOR_VERSION: - values[0] = alcMajorVersion; - return 1; - - case ALC_MINOR_VERSION: - values[0] = alcMinorVersion; - return 1; - - case ALC_EFX_MAJOR_VERSION: - values[0] = alcEFXMajorVersion; - return 1; - - case ALC_EFX_MINOR_VERSION: - values[0] = alcEFXMinorVersion; - return 1; - - case ALC_ATTRIBUTES_SIZE: - values[0] = 15; - return 1; - - case ALC_ALL_ATTRIBUTES: - if(size < 15) - { - alcSetError(device, ALC_INVALID_VALUE); - return 0; - } - - i = 0; - values[i++] = ALC_FREQUENCY; - values[i++] = device->Frequency; - - if(device->Type != Loopback) - { - values[i++] = ALC_REFRESH; - values[i++] = device->Frequency / device->UpdateSize; - - values[i++] = ALC_SYNC; - values[i++] = ALC_FALSE; - } - else - { - values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = device->FmtChans; - - values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = device->FmtType; - } - - values[i++] = ALC_MONO_SOURCES; - values[i++] = device->NumMonoSources; - - values[i++] = ALC_STEREO_SOURCES; - values[i++] = device->NumStereoSources; - - values[i++] = ALC_MAX_AUXILIARY_SENDS; - values[i++] = device->NumAuxSends; - - values[i++] = ALC_HRTF_SOFT; - values[i++] = (device->Hrtf ? ALC_TRUE : ALC_FALSE); - - values[i++] = 0; - return i; - - case ALC_FREQUENCY: - values[0] = device->Frequency; - return 1; - - case ALC_REFRESH: - if(device->Type == Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = device->Frequency / device->UpdateSize; - return 1; - - case ALC_SYNC: - if(device->Type == Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = ALC_FALSE; - return 1; - - case ALC_FORMAT_CHANNELS_SOFT: - if(device->Type != Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = device->FmtChans; - return 1; - - case ALC_FORMAT_TYPE_SOFT: - if(device->Type != Loopback) - { - alcSetError(device, ALC_INVALID_DEVICE); - return 0; - } - values[0] = device->FmtType; - return 1; - - case ALC_MONO_SOURCES: - values[0] = device->NumMonoSources; - return 1; - - case ALC_STEREO_SOURCES: - values[0] = device->NumStereoSources; - return 1; - - case ALC_MAX_AUXILIARY_SENDS: - values[0] = device->NumAuxSends; - return 1; - - case ALC_CONNECTED: - values[0] = device->Connected; - return 1; - - case ALC_HRTF_SOFT: - values[0] = (device->Hrtf ? ALC_TRUE : ALC_FALSE); - return 1; - - default: - alcSetError(device, ALC_INVALID_ENUM); - return 0; - } - return 0; -} - -/* alcGetIntegerv - * - * Returns information about the device and the version of OpenAL - */ -ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) -{ - device = VerifyDevice(device); - if(size <= 0 || values == NULL) - alcSetError(device, ALC_INVALID_VALUE); - else - GetIntegerv(device, param, size, values); - if(device) ALCdevice_DecRef(device); -} - -ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values) -{ - ALCint *ivals; - ALsizei i; - - device = VerifyDevice(device); - if(size <= 0 || values == NULL) - alcSetError(device, ALC_INVALID_VALUE); - else if(!device || device->Type == Capture) - { - ivals = malloc(size * sizeof(ALCint)); - size = GetIntegerv(device, pname, size, ivals); - for(i = 0;i < size;i++) - values[i] = ivals[i]; - free(ivals); - } - else /* render device */ - { - switch(pname) - { - case ALC_ATTRIBUTES_SIZE: - *values = 17; - break; - - case ALC_ALL_ATTRIBUTES: - if(size < 17) - alcSetError(device, ALC_INVALID_VALUE); - else - { - int i = 0; - - V0(device->Backend,lock)(); - values[i++] = ALC_FREQUENCY; - values[i++] = device->Frequency; - - if(device->Type != Loopback) - { - values[i++] = ALC_REFRESH; - values[i++] = device->Frequency / device->UpdateSize; - - values[i++] = ALC_SYNC; - values[i++] = ALC_FALSE; - } - else - { - values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = device->FmtChans; - - values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = device->FmtType; - } - - values[i++] = ALC_MONO_SOURCES; - values[i++] = device->NumMonoSources; - - values[i++] = ALC_STEREO_SOURCES; - values[i++] = device->NumStereoSources; - - values[i++] = ALC_MAX_AUXILIARY_SENDS; - values[i++] = device->NumAuxSends; - - values[i++] = ALC_HRTF_SOFT; - values[i++] = (device->Hrtf ? ALC_TRUE : ALC_FALSE); - - values[i++] = ALC_DEVICE_CLOCK_SOFT; - values[i++] = device->ClockBase + - (device->SamplesDone * DEVICE_CLOCK_RES / device->Frequency); - - values[i++] = 0; - V0(device->Backend,unlock)(); - } - break; - - case ALC_DEVICE_CLOCK_SOFT: - V0(device->Backend,lock)(); - *values = device->ClockBase + - (device->SamplesDone * DEVICE_CLOCK_RES / device->Frequency); - V0(device->Backend,unlock)(); - break; - - default: - ivals = malloc(size * sizeof(ALCint)); - size = GetIntegerv(device, pname, size, ivals); - for(i = 0;i < size;i++) - values[i] = ivals[i]; - free(ivals); - break; - } - } - if(device) - ALCdevice_DecRef(device); -} - - -/* alcIsExtensionPresent - * - * Determines if there is support for a particular extension - */ -ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) -{ - ALCboolean bResult = ALC_FALSE; - - device = VerifyDevice(device); - - if(!extName) - alcSetError(device, ALC_INVALID_VALUE); - else - { - size_t len = strlen(extName); - const char *ptr = (device ? alcExtensionList : alcNoDeviceExtList); - while(ptr && *ptr) - { - if(strncasecmp(ptr, extName, len) == 0 && - (ptr[len] == '\0' || isspace(ptr[len]))) - { - bResult = ALC_TRUE; - break; - } - if((ptr=strchr(ptr, ' ')) != NULL) - { - do { - ++ptr; - } while(isspace(*ptr)); - } - } - } - if(device) - ALCdevice_DecRef(device); - return bResult; -} - - -/* alcGetProcAddress - * - * Retrieves the function address for a particular extension function - */ -ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) -{ - ALCvoid *ptr = NULL; - - if(!funcName) - { - device = VerifyDevice(device); - alcSetError(device, ALC_INVALID_VALUE); - if(device) ALCdevice_DecRef(device); - } - else - { - ALsizei i = 0; - while(alcFunctions[i].funcName && strcmp(alcFunctions[i].funcName, funcName) != 0) - i++; - ptr = alcFunctions[i].address; - } - - return ptr; -} - - -/* alcGetEnumValue - * - * Get the value for a particular ALC enumeration name - */ -ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) -{ - ALCenum val = 0; - - if(!enumName) - { - device = VerifyDevice(device); - alcSetError(device, ALC_INVALID_VALUE); - if(device) ALCdevice_DecRef(device); - } - else - { - ALsizei i = 0; - while(enumeration[i].enumName && strcmp(enumeration[i].enumName, enumName) != 0) - i++; - val = enumeration[i].value; - } - - return val; -} - - -/* alcCreateContext - * - * Create and attach a context to the given device. - */ -ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) -{ - ALCcontext *ALContext; - ALCenum err; - - LockLists(); - if(!(device=VerifyDevice(device)) || device->Type == Capture || !device->Connected) - { - UnlockLists(); - alcSetError(device, ALC_INVALID_DEVICE); - if(device) ALCdevice_DecRef(device); - return NULL; - } - - ATOMIC_STORE(&device->LastError, ALC_NO_ERROR); - - if((err=UpdateDeviceParams(device, attrList)) != ALC_NO_ERROR) - { - UnlockLists(); - alcSetError(device, err); - if(err == ALC_INVALID_DEVICE) - { - ALCdevice_Lock(device); - aluHandleDisconnect(device); - ALCdevice_Unlock(device); - } - ALCdevice_DecRef(device); - return NULL; - } - - ALContext = al_calloc(16, sizeof(ALCcontext)+sizeof(ALlistener)); - if(ALContext) - { - InitRef(&ALContext->ref, 1); - ALContext->Listener = (ALlistener*)ALContext->_listener_mem; - - VECTOR_INIT(ALContext->ActiveAuxSlots); - - ALContext->VoiceCount = 0; - ALContext->MaxVoices = 256; - ALContext->Voices = al_calloc(16, ALContext->MaxVoices * sizeof(ALContext->Voices[0])); - } - if(!ALContext || !ALContext->Voices) - { - if(!ATOMIC_LOAD(&device->ContextList)) - { - V0(device->Backend,stop)(); - device->Flags &= ~DEVICE_RUNNING; - } - UnlockLists(); - - if(ALContext) - { - al_free(ALContext->Voices); - ALContext->Voices = NULL; - - VECTOR_DEINIT(ALContext->ActiveAuxSlots); - - al_free(ALContext); - ALContext = NULL; - } - - alcSetError(device, ALC_OUT_OF_MEMORY); - ALCdevice_DecRef(device); - return NULL; - } - - ALContext->Device = device; - ALCdevice_IncRef(device); - InitContext(ALContext); - - { - ALCcontext *head = ATOMIC_LOAD(&device->ContextList); - do { - ALContext->next = head; - } while(!ATOMIC_COMPARE_EXCHANGE_WEAK(ALCcontext*, &device->ContextList, &head, ALContext)); - } - UnlockLists(); - - ALCdevice_DecRef(device); - - TRACE("Created context %p\n", ALContext); - return ALContext; -} - -/* alcDestroyContext - * - * Remove a context from its device - */ -ALC_API ALCvoid ALC_APIENTRY alcDestroyContext(ALCcontext *context) -{ - ALCdevice *Device; - - LockLists(); - /* alcGetContextsDevice sets an error for invalid contexts */ - Device = alcGetContextsDevice(context); - if(Device) - { - ReleaseContext(context, Device); - if(!ATOMIC_LOAD(&Device->ContextList)) - { - V0(Device->Backend,stop)(); - Device->Flags &= ~DEVICE_RUNNING; - } - } - UnlockLists(); -} - - -/* alcGetCurrentContext - * - * Returns the currently active context on the calling thread - */ -ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) -{ - ALCcontext *Context = altss_get(LocalContext); - if(!Context) Context = ATOMIC_LOAD(&GlobalContext); - return Context; -} - -/* alcGetThreadContext - * - * Returns the currently active thread-local context - */ -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) -{ - return altss_get(LocalContext); -} - - -/* alcMakeContextCurrent - * - * Makes the given context the active process-wide context, and removes the - * thread-local context for the calling thread. - */ -ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) -{ - /* context must be valid or NULL */ - if(context && !(context=VerifyContext(context))) - { - alcSetError(NULL, ALC_INVALID_CONTEXT); - return ALC_FALSE; - } - /* context's reference count is already incremented */ - context = ATOMIC_EXCHANGE(ALCcontext*, &GlobalContext, context); - if(context) ALCcontext_DecRef(context); - - if((context=altss_get(LocalContext)) != NULL) - { - altss_set(LocalContext, NULL); - ALCcontext_DecRef(context); - } - - return ALC_TRUE; -} - -/* alcSetThreadContext - * - * Makes the given context the active context for the current thread - */ -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) -{ - ALCcontext *old; - - /* context must be valid or NULL */ - if(context && !(context=VerifyContext(context))) - { - alcSetError(NULL, ALC_INVALID_CONTEXT); - return ALC_FALSE; - } - /* context's reference count is already incremented */ - old = altss_get(LocalContext); - altss_set(LocalContext, context); - if(old) ALCcontext_DecRef(old); - - return ALC_TRUE; -} - - -/* alcGetContextsDevice - * - * Returns the device that a particular context is attached to - */ -ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context) -{ - ALCdevice *Device; - - if(!(Context=VerifyContext(Context))) - { - alcSetError(NULL, ALC_INVALID_CONTEXT); - return NULL; - } - Device = Context->Device; - ALCcontext_DecRef(Context); - - return Device; -} - - -/* alcOpenDevice - * - * Opens the named device. - */ -ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) -{ - const ALCchar *fmt; - ALCdevice *device; - ALCenum err; - - DO_INITCONFIG(); - - if(!PlaybackBackend.name) - { - alcSetError(NULL, ALC_INVALID_VALUE); - return NULL; - } - SuspendAndProcessSupported = strcasecmp(PlaybackBackend.name, "mmdevapi") == 0; - - if(deviceName && (!deviceName[0] || strcasecmp(deviceName, alcDefaultName) == 0 || strcasecmp(deviceName, "openal-soft") == 0)) - deviceName = NULL; - - device = al_calloc(16, sizeof(ALCdevice)+sizeof(ALeffectslot)); - if(!device) - { - alcSetError(NULL, ALC_OUT_OF_MEMORY); - return NULL; - } - - //Validate device - InitRef(&device->ref, 1); - device->Connected = ALC_TRUE; - device->Type = Playback; - ATOMIC_INIT(&device->LastError, ALC_NO_ERROR); - - device->Flags = 0; - device->Bs2b = NULL; - device->Bs2bLevel = 0; - AL_STRING_INIT(device->DeviceName); - - ATOMIC_INIT(&device->ContextList, NULL); - - device->ClockBase = 0; - device->SamplesDone = 0; - - device->MaxNoOfSources = 256; - device->AuxiliaryEffectSlotMax = 4; - device->NumAuxSends = MAX_SENDS; - - InitUIntMap(&device->BufferMap, ~0); - InitUIntMap(&device->EffectMap, ~0); - InitUIntMap(&device->FilterMap, ~0); - InitUIntMap(&device->SfontMap, ~0); - InitUIntMap(&device->PresetMap, ~0); - InitUIntMap(&device->FontsoundMap, ~0); - - //Set output format - device->FmtChans = DevFmtChannelsDefault; - device->FmtType = DevFmtTypeDefault; - device->Frequency = DEFAULT_OUTPUT_RATE; - device->NumUpdates = 4; - device->UpdateSize = 1024; - - if(!PlaybackBackend.getFactory) - device->Backend = create_backend_wrapper(device, &PlaybackBackend.Funcs, - ALCbackend_Playback); - else - { - ALCbackendFactory *factory = PlaybackBackend.getFactory(); - device->Backend = V(factory,createBackend)(device, ALCbackend_Playback); - } - if(!device->Backend) - { - al_free(device); - alcSetError(NULL, ALC_OUT_OF_MEMORY); - return NULL; - } - - - if(ConfigValueStr(NULL, "channels", &fmt)) - { - static const struct { - const char name[16]; - enum DevFmtChannels chans; - } chanlist[] = { - { "mono", DevFmtMono }, - { "stereo", DevFmtStereo }, - { "quad", DevFmtQuad }, - { "surround51", DevFmtX51 }, - { "surround61", DevFmtX61 }, - { "surround71", DevFmtX71 }, - }; - size_t i; - - for(i = 0;i < COUNTOF(chanlist);i++) - { - if(strcasecmp(chanlist[i].name, fmt) == 0) - { - device->FmtChans = chanlist[i].chans; - device->Flags |= DEVICE_CHANNELS_REQUEST; - break; - } - } - if(i == COUNTOF(chanlist)) - ERR("Unsupported channels: %s\n", fmt); - } - if(ConfigValueStr(NULL, "sample-type", &fmt)) - { - static const struct { - const char name[16]; - enum DevFmtType type; - } typelist[] = { - { "int8", DevFmtByte }, - { "uint8", DevFmtUByte }, - { "int16", DevFmtShort }, - { "uint16", DevFmtUShort }, - { "int32", DevFmtInt }, - { "uint32", DevFmtUInt }, - { "float32", DevFmtFloat }, - }; - size_t i; - - for(i = 0;i < COUNTOF(typelist);i++) - { - if(strcasecmp(typelist[i].name, fmt) == 0) - { - device->FmtType = typelist[i].type; - device->Flags |= DEVICE_SAMPLE_TYPE_REQUEST; - break; - } - } - if(i == COUNTOF(typelist)) - ERR("Unsupported sample-type: %s\n", fmt); - } -#define DEVICE_FORMAT_REQUEST (DEVICE_CHANNELS_REQUEST|DEVICE_SAMPLE_TYPE_REQUEST) - if((device->Flags&DEVICE_FORMAT_REQUEST) != DEVICE_FORMAT_REQUEST && - ConfigValueStr(NULL, "format", &fmt)) - { - static const struct { - const char name[32]; - enum DevFmtChannels channels; - enum DevFmtType type; - } formats[] = { - { "AL_FORMAT_MONO32", DevFmtMono, DevFmtFloat }, - { "AL_FORMAT_STEREO32", DevFmtStereo, DevFmtFloat }, - { "AL_FORMAT_QUAD32", DevFmtQuad, DevFmtFloat }, - { "AL_FORMAT_51CHN32", DevFmtX51, DevFmtFloat }, - { "AL_FORMAT_61CHN32", DevFmtX61, DevFmtFloat }, - { "AL_FORMAT_71CHN32", DevFmtX71, DevFmtFloat }, - - { "AL_FORMAT_MONO16", DevFmtMono, DevFmtShort }, - { "AL_FORMAT_STEREO16", DevFmtStereo, DevFmtShort }, - { "AL_FORMAT_QUAD16", DevFmtQuad, DevFmtShort }, - { "AL_FORMAT_51CHN16", DevFmtX51, DevFmtShort }, - { "AL_FORMAT_61CHN16", DevFmtX61, DevFmtShort }, - { "AL_FORMAT_71CHN16", DevFmtX71, DevFmtShort }, - - { "AL_FORMAT_MONO8", DevFmtMono, DevFmtByte }, - { "AL_FORMAT_STEREO8", DevFmtStereo, DevFmtByte }, - { "AL_FORMAT_QUAD8", DevFmtQuad, DevFmtByte }, - { "AL_FORMAT_51CHN8", DevFmtX51, DevFmtByte }, - { "AL_FORMAT_61CHN8", DevFmtX61, DevFmtByte }, - { "AL_FORMAT_71CHN8", DevFmtX71, DevFmtByte } - }; - size_t i; - - ERR("Option 'format' is deprecated, please use 'channels' and 'sample-type'\n"); - for(i = 0;i < COUNTOF(formats);i++) - { - if(strcasecmp(fmt, formats[i].name) == 0) - { - if(!(device->Flags&DEVICE_CHANNELS_REQUEST)) - device->FmtChans = formats[i].channels; - if(!(device->Flags&DEVICE_SAMPLE_TYPE_REQUEST)) - device->FmtType = formats[i].type; - device->Flags |= DEVICE_FORMAT_REQUEST; - break; - } - } - if(i == COUNTOF(formats)) - ERR("Unsupported format: %s\n", fmt); - } -#undef DEVICE_FORMAT_REQUEST - - if(ConfigValueUInt(NULL, "frequency", &device->Frequency)) - { - device->Flags |= DEVICE_FREQUENCY_REQUEST; - if(device->Frequency < MIN_OUTPUT_RATE) - ERR("%uhz request clamped to %uhz minimum\n", device->Frequency, MIN_OUTPUT_RATE); - device->Frequency = maxu(device->Frequency, MIN_OUTPUT_RATE); - } - - ConfigValueUInt(NULL, "periods", &device->NumUpdates); - device->NumUpdates = clampu(device->NumUpdates, 2, 16); - - ConfigValueUInt(NULL, "period_size", &device->UpdateSize); - device->UpdateSize = clampu(device->UpdateSize, 64, 8192); - if((CPUCapFlags&CPU_CAP_SSE)) - device->UpdateSize = (device->UpdateSize+3)&~3; - - ConfigValueUInt(NULL, "sources", &device->MaxNoOfSources); - if(device->MaxNoOfSources == 0) device->MaxNoOfSources = 256; - - ConfigValueUInt(NULL, "slots", &device->AuxiliaryEffectSlotMax); - if(device->AuxiliaryEffectSlotMax == 0) device->AuxiliaryEffectSlotMax = 4; - - ConfigValueUInt(NULL, "sends", &device->NumAuxSends); - if(device->NumAuxSends > MAX_SENDS) device->NumAuxSends = MAX_SENDS; - - ConfigValueInt(NULL, "cf_level", &device->Bs2bLevel); - - device->NumStereoSources = 1; - device->NumMonoSources = device->MaxNoOfSources - device->NumStereoSources; - - device->Synth = SynthCreate(device); - if(!device->Synth) - { - DELETE_OBJ(device->Backend); - al_free(device); - alcSetError(NULL, ALC_OUT_OF_MEMORY); - return NULL; - } - - // Find a playback device to open - if((err=V(device->Backend,open)(deviceName)) != ALC_NO_ERROR) - { - DELETE_OBJ(device->Synth); - DELETE_OBJ(device->Backend); - al_free(device); - alcSetError(NULL, err); - return NULL; - } - - if(DefaultEffect.type != AL_EFFECT_NULL) - { - device->DefaultSlot = (ALeffectslot*)device->_slot_mem; - if(InitEffectSlot(device->DefaultSlot) != AL_NO_ERROR) - { - device->DefaultSlot = NULL; - ERR("Failed to initialize the default effect slot\n"); - } - else if(InitializeEffect(device, device->DefaultSlot, &DefaultEffect) != AL_NO_ERROR) - { - ALeffectState *state = device->DefaultSlot->EffectState; - device->DefaultSlot = NULL; - DELETE_OBJ(state); - ERR("Failed to initialize the default effect\n"); - } - } - - { - ALCdevice *head = ATOMIC_LOAD(&DeviceList); - do { - device->next = head; - } while(!ATOMIC_COMPARE_EXCHANGE_WEAK(ALCdevice*, &DeviceList, &head, device)); - } - - TRACE("Created device %p, \"%s\"\n", device, al_string_get_cstr(device->DeviceName)); - return device; -} - -/* alcCloseDevice - * - * Closes the given device. - */ -ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) -{ - ALCdevice *list, *origdev, *nextdev; - ALCcontext *ctx; - - LockLists(); - list = ATOMIC_LOAD(&DeviceList); - do { - if(list == device) - break; - } while((list=list->next) != NULL); - if(!list || list->Type == Capture) - { - alcSetError(list, ALC_INVALID_DEVICE); - UnlockLists(); - return ALC_FALSE; - } - - origdev = device; - nextdev = device->next; - if(!ATOMIC_COMPARE_EXCHANGE_STRONG(ALCdevice*, &DeviceList, &origdev, nextdev)) - { - do { - list = origdev; - origdev = device; - } while(!COMPARE_EXCHANGE(&list->next, &origdev, nextdev)); - } - UnlockLists(); - - ctx = ATOMIC_LOAD(&device->ContextList); - while(ctx != NULL) - { - ALCcontext *next = ctx->next; - WARN("Releasing context %p\n", ctx); - ReleaseContext(ctx, device); - ctx = next; - } - if((device->Flags&DEVICE_RUNNING)) - V0(device->Backend,stop)(); - device->Flags &= ~DEVICE_RUNNING; - - ALCdevice_DecRef(device); - - return ALC_TRUE; -} - - -/************************************************ - * ALC capture functions - ************************************************/ -ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples) -{ - ALCdevice *device = NULL; - ALCenum err; - - DO_INITCONFIG(); - - if(!CaptureBackend.name) - { - alcSetError(NULL, ALC_INVALID_VALUE); - return NULL; - } - - if(samples <= 0) - { - alcSetError(NULL, ALC_INVALID_VALUE); - return NULL; - } - - if(deviceName && (!deviceName[0] || strcasecmp(deviceName, alcDefaultName) == 0 || strcasecmp(deviceName, "openal-soft") == 0)) - deviceName = NULL; - - device = al_calloc(16, sizeof(ALCdevice)); - if(!device) - { - alcSetError(NULL, ALC_OUT_OF_MEMORY); - return NULL; - } - - //Validate device - InitRef(&device->ref, 1); - device->Connected = ALC_TRUE; - device->Type = Capture; - - AL_STRING_INIT(device->DeviceName); - - InitUIntMap(&device->BufferMap, ~0); - InitUIntMap(&device->EffectMap, ~0); - InitUIntMap(&device->FilterMap, ~0); - InitUIntMap(&device->SfontMap, ~0); - InitUIntMap(&device->PresetMap, ~0); - InitUIntMap(&device->FontsoundMap, ~0); - - if(!CaptureBackend.getFactory) - device->Backend = create_backend_wrapper(device, &CaptureBackend.Funcs, - ALCbackend_Capture); - else - { - ALCbackendFactory *factory = CaptureBackend.getFactory(); - device->Backend = V(factory,createBackend)(device, ALCbackend_Capture); - } - if(!device->Backend) - { - al_free(device); - alcSetError(NULL, ALC_OUT_OF_MEMORY); - return NULL; - } - - device->Flags |= DEVICE_FREQUENCY_REQUEST; - device->Frequency = frequency; - - device->Flags |= DEVICE_CHANNELS_REQUEST | DEVICE_SAMPLE_TYPE_REQUEST; - if(DecomposeDevFormat(format, &device->FmtChans, &device->FmtType) == AL_FALSE) - { - al_free(device); - alcSetError(NULL, ALC_INVALID_ENUM); - return NULL; - } - - device->UpdateSize = samples; - device->NumUpdates = 1; - - if((err=V(device->Backend,open)(deviceName)) != ALC_NO_ERROR) - { - al_free(device); - alcSetError(NULL, err); - return NULL; - } - - { - ALCdevice *head = ATOMIC_LOAD(&DeviceList); - do { - device->next = head; - } while(!ATOMIC_COMPARE_EXCHANGE_WEAK(ALCdevice*, &DeviceList, &head, device)); - } - - TRACE("Created device %p, \"%s\"\n", device, al_string_get_cstr(device->DeviceName)); - return device; -} - -ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) -{ - ALCdevice *list, *next, *nextdev; - - LockLists(); - list = ATOMIC_LOAD(&DeviceList); - do { - if(list == device) - break; - } while((list=list->next) != NULL); - if(!list || list->Type != Capture) - { - alcSetError(list, ALC_INVALID_DEVICE); - UnlockLists(); - return ALC_FALSE; - } - - next = device; - nextdev = device->next; - if(!ATOMIC_COMPARE_EXCHANGE_STRONG(ALCdevice*, &DeviceList, &next, nextdev)) - { - do { - list = next; - next = device; - } while(!COMPARE_EXCHANGE(&list->next, &next, nextdev)); - } - UnlockLists(); - - ALCdevice_DecRef(device); - - return ALC_TRUE; -} - -ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) -{ - if(!(device=VerifyDevice(device)) || device->Type != Capture) - alcSetError(device, ALC_INVALID_DEVICE); - else - { - ALCdevice_Lock(device); - if(device->Connected) - { - if(!(device->Flags&DEVICE_RUNNING)) - V0(device->Backend,start)(); - device->Flags |= DEVICE_RUNNING; - } - ALCdevice_Unlock(device); - } - - if(device) ALCdevice_DecRef(device); -} - -ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) -{ - if(!(device=VerifyDevice(device)) || device->Type != Capture) - alcSetError(device, ALC_INVALID_DEVICE); - else - { - ALCdevice_Lock(device); - if((device->Flags&DEVICE_RUNNING)) - V0(device->Backend,stop)(); - device->Flags &= ~DEVICE_RUNNING; - ALCdevice_Unlock(device); - } - - if(device) ALCdevice_DecRef(device); -} - -ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) -{ - if(!(device=VerifyDevice(device)) || device->Type != Capture) - alcSetError(device, ALC_INVALID_DEVICE); - else - { - ALCenum err = ALC_INVALID_VALUE; - - ALCdevice_Lock(device); - if(samples >= 0 && V0(device->Backend,availableSamples)() >= (ALCuint)samples) - err = V(device->Backend,captureSamples)(buffer, samples); - ALCdevice_Unlock(device); - - if(err != ALC_NO_ERROR) - alcSetError(device, err); - } - if(device) ALCdevice_DecRef(device); -} - - -/************************************************ - * ALC loopback functions - ************************************************/ - -/* alcLoopbackOpenDeviceSOFT - * - * Open a loopback device, for manual rendering. - */ -ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) -{ - ALCbackendFactory *factory; - ALCdevice *device; - - DO_INITCONFIG(); - - /* Make sure the device name, if specified, is us. */ - if(deviceName && strcmp(deviceName, alcDefaultName) != 0) - { - alcSetError(NULL, ALC_INVALID_VALUE); - return NULL; - } - - device = al_calloc(16, sizeof(ALCdevice)); - if(!device) - { - alcSetError(NULL, ALC_OUT_OF_MEMORY); - return NULL; - } - - //Validate device - InitRef(&device->ref, 1); - device->Connected = ALC_TRUE; - device->Type = Loopback; - ATOMIC_INIT(&device->LastError, ALC_NO_ERROR); - - device->Flags = 0; - device->Bs2b = NULL; - device->Bs2bLevel = 0; - AL_STRING_INIT(device->DeviceName); - - ATOMIC_INIT(&device->ContextList, NULL); - - device->ClockBase = 0; - device->SamplesDone = 0; - - device->MaxNoOfSources = 256; - device->AuxiliaryEffectSlotMax = 4; - device->NumAuxSends = MAX_SENDS; - - InitUIntMap(&device->BufferMap, ~0); - InitUIntMap(&device->EffectMap, ~0); - InitUIntMap(&device->FilterMap, ~0); - InitUIntMap(&device->SfontMap, ~0); - InitUIntMap(&device->PresetMap, ~0); - InitUIntMap(&device->FontsoundMap, ~0); - - factory = ALCloopbackFactory_getFactory(); - device->Backend = V(factory,createBackend)(device, ALCbackend_Loopback); - if(!device->Backend) - { - al_free(device); - alcSetError(NULL, ALC_OUT_OF_MEMORY); - return NULL; - } - - //Set output format - device->NumUpdates = 0; - device->UpdateSize = 0; - - device->Frequency = DEFAULT_OUTPUT_RATE; - device->FmtChans = DevFmtChannelsDefault; - device->FmtType = DevFmtTypeDefault; - - ConfigValueUInt(NULL, "sources", &device->MaxNoOfSources); - if(device->MaxNoOfSources == 0) device->MaxNoOfSources = 256; - - ConfigValueUInt(NULL, "slots", &device->AuxiliaryEffectSlotMax); - if(device->AuxiliaryEffectSlotMax == 0) device->AuxiliaryEffectSlotMax = 4; - - ConfigValueUInt(NULL, "sends", &device->NumAuxSends); - if(device->NumAuxSends > MAX_SENDS) device->NumAuxSends = MAX_SENDS; - - device->NumStereoSources = 1; - device->NumMonoSources = device->MaxNoOfSources - device->NumStereoSources; - - device->Synth = SynthCreate(device); - if(!device->Synth) - { - DELETE_OBJ(device->Backend); - al_free(device); - alcSetError(NULL, ALC_OUT_OF_MEMORY); - return NULL; - } - - // Open the "backend" - V(device->Backend,open)("Loopback"); - - { - ALCdevice *head = ATOMIC_LOAD(&DeviceList); - do { - device->next = head; - } while(!ATOMIC_COMPARE_EXCHANGE_WEAK(ALCdevice*, &DeviceList, &head, device)); - } - - TRACE("Created device %p\n", device); - return device; -} - -/* alcIsRenderFormatSupportedSOFT - * - * Determines if the loopback device supports the given format for rendering. - */ -ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) -{ - ALCboolean ret = ALC_FALSE; - - if(!(device=VerifyDevice(device)) || device->Type != Loopback) - alcSetError(device, ALC_INVALID_DEVICE); - else if(freq <= 0) - alcSetError(device, ALC_INVALID_VALUE); - else - { - if(IsValidALCType(type) && BytesFromDevFmt(type) > 0 && - IsValidALCChannels(channels) && ChannelsFromDevFmt(channels) > 0 && - freq >= MIN_OUTPUT_RATE) - ret = ALC_TRUE; - } - if(device) ALCdevice_DecRef(device); - - return ret; -} - -/* alcRenderSamplesSOFT - * - * Renders some samples into a buffer, using the format last set by the - * attributes given to alcCreateContext. - */ -FORCE_ALIGN ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) -{ - if(!(device=VerifyDevice(device)) || device->Type != Loopback) - alcSetError(device, ALC_INVALID_DEVICE); - else if(samples < 0 || (samples > 0 && buffer == NULL)) - alcSetError(device, ALC_INVALID_VALUE); - else - aluMixData(device, buffer, samples); - if(device) ALCdevice_DecRef(device); -} - - -/************************************************ - * ALC DSP pause/resume functions - ************************************************/ - -/* alcDevicePauseSOFT - * - * Pause the DSP to stop audio processing. - */ -ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) -{ - if(!(device=VerifyDevice(device)) || device->Type != Playback) - alcSetError(device, ALC_INVALID_DEVICE); - else - { - LockLists(); - if((device->Flags&DEVICE_RUNNING)) - V0(device->Backend,stop)(); - device->Flags &= ~DEVICE_RUNNING; - device->Flags |= DEVICE_PAUSED; - UnlockLists(); - } - if(device) ALCdevice_DecRef(device); -} - -/* alcDeviceResumeSOFT - * - * Resume the DSP to restart audio processing. - */ -ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) -{ - if(!(device=VerifyDevice(device)) || device->Type != Playback) - alcSetError(device, ALC_INVALID_DEVICE); - else - { - LockLists(); - if((device->Flags&DEVICE_PAUSED)) - { - device->Flags &= ~DEVICE_PAUSED; - if(ATOMIC_LOAD(&device->ContextList) != NULL) - { - if(V0(device->Backend,start)() != ALC_FALSE) - device->Flags |= DEVICE_RUNNING; - else - { - alcSetError(device, ALC_INVALID_DEVICE); - ALCdevice_Lock(device); - aluHandleDisconnect(device); - ALCdevice_Unlock(device); - } - } - } - UnlockLists(); - } - if(device) ALCdevice_DecRef(device); -} diff --git a/Telegram/_openal_patch/Alc/backends/mmdevapi.c b/Telegram/_openal_patch/Alc/backends/mmdevapi.c new file mode 100644 index 0000000000..8a6f9fbadf --- /dev/null +++ b/Telegram/_openal_patch/Alc/backends/mmdevapi.c @@ -0,0 +1,1789 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2011 by authors. + * 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; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#define COBJMACROS +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WAVEFORMATEXTENSIBLE_ +#include +#include +#endif + +#include "alMain.h" +#include "alu.h" +#include "threads.h" +#include "compat.h" +#include "alstring.h" + +#include "backends/base.h" + + +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); + +DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); +DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); + +#define MONO SPEAKER_FRONT_CENTER +#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) +#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) +#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) +#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X7DOT1_WIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_FRONT_LEFT_OF_CENTER|SPEAKER_FRONT_RIGHT_OF_CENTER) + + +typedef struct { + al_string name; + WCHAR *devid; +} DevMap; +TYPEDEF_VECTOR(DevMap, vector_DevMap) + +static void clear_devlist(vector_DevMap *list) +{ +#define CLEAR_DEVMAP(i) do { \ + AL_STRING_DEINIT((i)->name); \ + free((i)->devid); \ + (i)->devid = NULL; \ +} while(0) + VECTOR_FOR_EACH(DevMap, *list, CLEAR_DEVMAP); + VECTOR_RESIZE(*list, 0); +#undef CLEAR_DEVMAP +} + +static vector_DevMap PlaybackDevices; +static vector_DevMap CaptureDevices; + + +static HANDLE ThreadHdl; +static DWORD ThreadID; + +typedef struct { + HANDLE FinishedEvt; + HRESULT result; +} ThreadRequest; + +#define WM_USER_First (WM_USER+0) +#define WM_USER_OpenDevice (WM_USER+0) +#define WM_USER_ResetDevice (WM_USER+1) +#define WM_USER_StartDevice (WM_USER+2) +#define WM_USER_StopDevice (WM_USER+3) +#define WM_USER_CloseDevice (WM_USER+4) +#define WM_USER_Enumerate (WM_USER+5) +#define WM_USER_Last (WM_USER+5) + +static inline void ReturnMsgResponse(ThreadRequest *req, HRESULT res) +{ + req->result = res; + SetEvent(req->FinishedEvt); +} + +static HRESULT WaitForResponse(ThreadRequest *req) +{ + if(WaitForSingleObject(req->FinishedEvt, INFINITE) == WAIT_OBJECT_0) + return req->result; + ERR("Message response error: %lu\n", GetLastError()); + return E_FAIL; +} + + +static void get_device_name(IMMDevice *device, al_string *name) +{ + IPropertyStore *ps; + PROPVARIANT pvname; + HRESULT hr; + + hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps); + if(FAILED(hr)) + { + WARN("OpenPropertyStore failed: 0x%08lx\n", hr); + return; + } + + PropVariantInit(&pvname); + + hr = IPropertyStore_GetValue(ps, (const PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &pvname); + if(FAILED(hr)) + WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); + else if(pvname.vt == VT_LPWSTR) + al_string_copy_wcstr(name, pvname.pwszVal); + else + WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvname.vt); + + PropVariantClear(&pvname); + IPropertyStore_Release(ps); +} + +static void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor) +{ + IPropertyStore *ps; + PROPVARIANT pvform; + HRESULT hr; + + hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps); + if(FAILED(hr)) + { + WARN("OpenPropertyStore failed: 0x%08lx\n", hr); + return; + } + + PropVariantInit(&pvform); + + hr = IPropertyStore_GetValue(ps, &PKEY_AudioEndpoint_FormFactor, &pvform); + if(FAILED(hr)) + WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr); + else if(pvform.vt == VT_UI4) + *formfactor = pvform.ulVal; + else if(pvform.vt == VT_EMPTY) + *formfactor = UnknownFormFactor; + else + WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.vt); + + PropVariantClear(&pvform); + IPropertyStore_Release(ps); +} + + +static void add_device(IMMDevice *device, LPCWSTR devid, vector_DevMap *list) +{ + DevMap entry; + + AL_STRING_INIT(entry.name); + entry.devid = strdupW(devid); + get_device_name(device, &entry.name); + + TRACE("Got device \"%s\", \"%ls\"\n", al_string_get_cstr(entry.name), entry.devid); + VECTOR_PUSH_BACK(*list, entry); +} + +static LPWSTR get_device_id(IMMDevice *device) +{ + LPWSTR devid; + HRESULT hr; + + hr = IMMDevice_GetId(device, &devid); + if(FAILED(hr)) + { + ERR("Failed to get device id: %lx\n", hr); + return NULL; + } + + return devid; +} + +static HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, vector_DevMap *list) +{ + IMMDeviceCollection *coll; + IMMDevice *defdev = NULL; + LPWSTR defdevid = NULL; + HRESULT hr; + UINT count; + UINT i; + + hr = IMMDeviceEnumerator_EnumAudioEndpoints(devenum, flowdir, DEVICE_STATE_ACTIVE, &coll); + if(FAILED(hr)) + { + ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); + return hr; + } + + count = 0; + hr = IMMDeviceCollection_GetCount(coll, &count); + if(SUCCEEDED(hr) && count > 0) + { + clear_devlist(list); + if(!VECTOR_RESERVE(*list, count)) + { + IMMDeviceCollection_Release(coll); + return E_OUTOFMEMORY; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devenum, flowdir, + eMultimedia, &defdev); + } + if(SUCCEEDED(hr) && defdev != NULL) + { + defdevid = get_device_id(defdev); + if(defdevid) + add_device(defdev, defdevid, list); + } + + for(i = 0;i < count;++i) + { + IMMDevice *device; + LPWSTR devid; + + hr = IMMDeviceCollection_Item(coll, i, &device); + if(FAILED(hr)) continue; + + devid = get_device_id(device); + if(devid) + { + if(wcscmp(devid, defdevid) != 0) + add_device(device, devid, list); + CoTaskMemFree(devid); + } + IMMDevice_Release(device); + } + + if(defdev) IMMDevice_Release(defdev); + if(defdevid) CoTaskMemFree(defdevid); + IMMDeviceCollection_Release(coll); + + return S_OK; +} + + +/* Proxy interface used by the message handler. */ +struct ALCmmdevProxyVtable; + +typedef struct ALCmmdevProxy { + const struct ALCmmdevProxyVtable *vtbl; +} ALCmmdevProxy; + +struct ALCmmdevProxyVtable { + HRESULT (*const openProxy)(ALCmmdevProxy*); + void (*const closeProxy)(ALCmmdevProxy*); + + HRESULT (*const resetProxy)(ALCmmdevProxy*); + HRESULT (*const startProxy)(ALCmmdevProxy*); + void (*const stopProxy)(ALCmmdevProxy*); +}; + +#define DEFINE_ALCMMDEVPROXY_VTABLE(T) \ +DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, openProxy) \ +DECLARE_THUNK(T, ALCmmdevProxy, void, closeProxy) \ +DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, resetProxy) \ +DECLARE_THUNK(T, ALCmmdevProxy, HRESULT, startProxy) \ +DECLARE_THUNK(T, ALCmmdevProxy, void, stopProxy) \ + \ +static const struct ALCmmdevProxyVtable T##_ALCmmdevProxy_vtable = { \ + T##_ALCmmdevProxy_openProxy, \ + T##_ALCmmdevProxy_closeProxy, \ + T##_ALCmmdevProxy_resetProxy, \ + T##_ALCmmdevProxy_startProxy, \ + T##_ALCmmdevProxy_stopProxy, \ +} + +static void ALCmmdevProxy_Construct(ALCmmdevProxy* UNUSED(self)) { } +static void ALCmmdevProxy_Destruct(ALCmmdevProxy* UNUSED(self)) { } + +static DWORD CALLBACK ALCmmdevProxy_messageHandler(void *ptr) +{ + ThreadRequest *req = ptr; + IMMDeviceEnumerator *Enumerator; + ALuint deviceCount = 0; + ALCmmdevProxy *proxy; + HRESULT hr, cohr; + MSG msg; + + TRACE("Starting message thread\n"); + + cohr = CoInitialize(NULL); + if(FAILED(cohr)) + { + WARN("Failed to initialize COM: 0x%08lx\n", cohr); + ReturnMsgResponse(req, cohr); + return 0; + } + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr); + if(FAILED(hr)) + { + WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + CoUninitialize(); + ReturnMsgResponse(req, hr); + return 0; + } + Enumerator = ptr; + IMMDeviceEnumerator_Release(Enumerator); + Enumerator = NULL; + + CoUninitialize(); + + /* HACK: Force Windows to create a message queue for this thread before + * returning success, otherwise PostThreadMessage may fail if it gets + * called before GetMessage. + */ + PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); + + TRACE("Message thread initialization complete\n"); + ReturnMsgResponse(req, S_OK); + + TRACE("Starting message loop\n"); + while(GetMessage(&msg, NULL, WM_USER_First, WM_USER_Last)) + { + TRACE("Got message %u (lparam=%p, wparam=%p)\n", msg.message, (void*)msg.lParam, (void*)msg.wParam); + switch(msg.message) + { + case WM_USER_OpenDevice: + req = (ThreadRequest*)msg.wParam; + proxy = (ALCmmdevProxy*)msg.lParam; + + hr = cohr = S_OK; + if(++deviceCount == 1) + hr = cohr = CoInitialize(NULL); + if(SUCCEEDED(hr)) + hr = V0(proxy,openProxy)(); + if(FAILED(hr)) + { + if(--deviceCount == 0 && SUCCEEDED(cohr)) + CoUninitialize(); + } + + ReturnMsgResponse(req, hr); + continue; + + case WM_USER_ResetDevice: + req = (ThreadRequest*)msg.wParam; + proxy = (ALCmmdevProxy*)msg.lParam; + + hr = V0(proxy,resetProxy)(); + ReturnMsgResponse(req, hr); + continue; + + case WM_USER_StartDevice: + req = (ThreadRequest*)msg.wParam; + proxy = (ALCmmdevProxy*)msg.lParam; + + hr = V0(proxy,startProxy)(); + ReturnMsgResponse(req, hr); + continue; + + case WM_USER_StopDevice: + req = (ThreadRequest*)msg.wParam; + proxy = (ALCmmdevProxy*)msg.lParam; + + V0(proxy,stopProxy)(); + ReturnMsgResponse(req, S_OK); + continue; + + case WM_USER_CloseDevice: + req = (ThreadRequest*)msg.wParam; + proxy = (ALCmmdevProxy*)msg.lParam; + + V0(proxy,closeProxy)(); + if(--deviceCount == 0) + CoUninitialize(); + + ReturnMsgResponse(req, S_OK); + continue; + + case WM_USER_Enumerate: + req = (ThreadRequest*)msg.wParam; + + hr = cohr = S_OK; + if(++deviceCount == 1) + hr = cohr = CoInitialize(NULL); + if(SUCCEEDED(hr)) + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr); + if(SUCCEEDED(hr)) + { + Enumerator = ptr; + + if(msg.lParam == ALL_DEVICE_PROBE) + hr = probe_devices(Enumerator, eRender, &PlaybackDevices); + else if(msg.lParam == CAPTURE_DEVICE_PROBE) + hr = probe_devices(Enumerator, eCapture, &CaptureDevices); + + IMMDeviceEnumerator_Release(Enumerator); + Enumerator = NULL; + } + + if(--deviceCount == 0 && SUCCEEDED(cohr)) + CoUninitialize(); + + ReturnMsgResponse(req, hr); + continue; + + default: + ERR("Unexpected message: %u\n", msg.message); + continue; + } + } + TRACE("Message loop finished\n"); + + return 0; +} + + +typedef struct ALCmmdevPlayback { + DERIVE_FROM_TYPE(ALCbackend); + DERIVE_FROM_TYPE(ALCmmdevProxy); + + WCHAR *devid; + + IMMDevice *mmdev; + IAudioClient *client; + IAudioRenderClient *render; + HANDLE NotifyEvent; + + HANDLE MsgEvent; + + volatile UINT32 Padding; + + volatile int killNow; + althrd_t thread; +} ALCmmdevPlayback; + +static int ALCmmdevPlayback_mixerProc(void *arg); + +static void ALCmmdevPlayback_Construct(ALCmmdevPlayback *self, ALCdevice *device); +static void ALCmmdevPlayback_Destruct(ALCmmdevPlayback *self); +static ALCenum ALCmmdevPlayback_open(ALCmmdevPlayback *self, const ALCchar *name); +static HRESULT ALCmmdevPlayback_openProxy(ALCmmdevPlayback *self); +static void ALCmmdevPlayback_close(ALCmmdevPlayback *self); +static void ALCmmdevPlayback_closeProxy(ALCmmdevPlayback *self); +static ALCboolean ALCmmdevPlayback_reset(ALCmmdevPlayback *self); +static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self); +static ALCboolean ALCmmdevPlayback_start(ALCmmdevPlayback *self); +static HRESULT ALCmmdevPlayback_startProxy(ALCmmdevPlayback *self); +static void ALCmmdevPlayback_stop(ALCmmdevPlayback *self); +static void ALCmmdevPlayback_stopProxy(ALCmmdevPlayback *self); +static DECLARE_FORWARD2(ALCmmdevPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint) +static DECLARE_FORWARD(ALCmmdevPlayback, ALCbackend, ALCuint, availableSamples) +static ALint64 ALCmmdevPlayback_getLatency(ALCmmdevPlayback *self); +static DECLARE_FORWARD(ALCmmdevPlayback, ALCbackend, void, lock) +static DECLARE_FORWARD(ALCmmdevPlayback, ALCbackend, void, unlock) +DECLARE_DEFAULT_ALLOCATORS(ALCmmdevPlayback) + +DEFINE_ALCMMDEVPROXY_VTABLE(ALCmmdevPlayback); +DEFINE_ALCBACKEND_VTABLE(ALCmmdevPlayback); + + +static void ALCmmdevPlayback_Construct(ALCmmdevPlayback *self, ALCdevice *device) +{ + SET_VTABLE2(ALCmmdevPlayback, ALCbackend, self); + SET_VTABLE2(ALCmmdevPlayback, ALCmmdevProxy, self); + ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device); + ALCmmdevProxy_Construct(STATIC_CAST(ALCmmdevProxy, self)); + + self->devid = NULL; + + self->mmdev = NULL; + self->client = NULL; + self->render = NULL; + self->NotifyEvent = NULL; + + self->MsgEvent = NULL; + + self->Padding = 0; + + self->killNow = 0; +} + +static void ALCmmdevPlayback_Destruct(ALCmmdevPlayback *self) +{ + if(self->NotifyEvent != NULL) + CloseHandle(self->NotifyEvent); + self->NotifyEvent = NULL; + if(self->MsgEvent != NULL) + CloseHandle(self->MsgEvent); + self->MsgEvent = NULL; + + free(self->devid); + self->devid = NULL; + + ALCmmdevProxy_Destruct(STATIC_CAST(ALCmmdevProxy, self)); + ALCbackend_Destruct(STATIC_CAST(ALCbackend, self)); +} + + +FORCE_ALIGN static int ALCmmdevPlayback_mixerProc(void *arg) +{ + ALCmmdevPlayback *self = arg; + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + UINT32 buffer_len, written; + ALuint update_size, len; + BYTE *buffer; + HRESULT hr; + + hr = CoInitialize(NULL); + if(FAILED(hr)) + { + ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr); + V0(device->Backend,lock)(); + aluHandleDisconnect(device); + V0(device->Backend,unlock)(); + return 1; + } + + SetRTPriority(); + althrd_setname(althrd_current(), MIXER_THREAD_NAME); + + update_size = device->UpdateSize; + buffer_len = update_size * device->NumUpdates; + while(!self->killNow) + { + hr = IAudioClient_GetCurrentPadding(self->client, &written); + if(FAILED(hr)) + { + ERR("Failed to get padding: 0x%08lx\n", hr); + V0(device->Backend,lock)(); + aluHandleDisconnect(device); + V0(device->Backend,unlock)(); + break; + } + self->Padding = written; + + len = buffer_len - written; + if(len < update_size) + { + DWORD res; + res = WaitForSingleObjectEx(self->NotifyEvent, 2000, FALSE); + if(res != WAIT_OBJECT_0) + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + continue; + } + len -= len%update_size; + + hr = IAudioRenderClient_GetBuffer(self->render, len, &buffer); + if(SUCCEEDED(hr)) + { + V0(device->Backend,lock)(); + aluMixData(device, buffer, len); + self->Padding = written + len; + V0(device->Backend,unlock)(); + hr = IAudioRenderClient_ReleaseBuffer(self->render, len, 0); + } + if(FAILED(hr)) + { + ERR("Failed to buffer data: 0x%08lx\n", hr); + V0(device->Backend,lock)(); + aluHandleDisconnect(device); + V0(device->Backend,unlock)(); + break; + } + } + self->Padding = 0; + + CoUninitialize(); + return 0; +} + + +static ALCboolean MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) +{ + memset(out, 0, sizeof(*out)); + if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + *out = *(const WAVEFORMATEXTENSIBLE*)in; + else if(in->wFormatTag == WAVE_FORMAT_PCM) + { + out->Format = *in; + out->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + out->Format.cbSize = sizeof(*out) - sizeof(*in); + if(out->Format.nChannels == 1) + out->dwChannelMask = MONO; + else if(out->Format.nChannels == 2) + out->dwChannelMask = STEREO; + else + ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels); + out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) + { + out->Format = *in; + out->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + out->Format.cbSize = sizeof(*out) - sizeof(*in); + if(out->Format.nChannels == 1) + out->dwChannelMask = MONO; + else if(out->Format.nChannels == 2) + out->dwChannelMask = STEREO; + else + ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels); + out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } + else + { + ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag); + return ALC_FALSE; + } + return ALC_TRUE; +} + + +static ALCenum ALCmmdevPlayback_open(ALCmmdevPlayback *self, const ALCchar *deviceName) +{ + HRESULT hr = S_OK; + + self->NotifyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + self->MsgEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if(self->NotifyEvent == NULL || self->MsgEvent == NULL) + { + ERR("Failed to create message events: %lu\n", GetLastError()); + hr = E_FAIL; + } + + if(SUCCEEDED(hr)) + { + if(deviceName) + { + const DevMap *iter, *end; + + if(VECTOR_SIZE(PlaybackDevices) == 0) + { + ThreadRequest req = { self->MsgEvent, 0 }; + if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, ALL_DEVICE_PROBE)) + (void)WaitForResponse(&req); + } + + hr = E_FAIL; + iter = VECTOR_ITER_BEGIN(PlaybackDevices); + end = VECTOR_ITER_END(PlaybackDevices); + for(;iter != end;iter++) + { + if(al_string_cmp_cstr(iter->name, deviceName) == 0) + { + self->devid = strdupW(iter->devid); + hr = S_OK; + break; + } + } + if(FAILED(hr)) + WARN("Failed to find device name matching \"%s\"\n", deviceName); + } + } + + if(SUCCEEDED(hr)) + { + ThreadRequest req = { self->MsgEvent, 0 }; + + hr = E_FAIL; + if(PostThreadMessage(ThreadID, WM_USER_OpenDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + hr = WaitForResponse(&req); + else + ERR("Failed to post thread message: %lu\n", GetLastError()); + } + + if(FAILED(hr)) + { + if(self->NotifyEvent != NULL) + CloseHandle(self->NotifyEvent); + self->NotifyEvent = NULL; + if(self->MsgEvent != NULL) + CloseHandle(self->MsgEvent); + self->MsgEvent = NULL; + + free(self->devid); + self->devid = NULL; + + ERR("Device init failed: 0x%08lx\n", hr); + return ALC_INVALID_VALUE; + } + + return ALC_NO_ERROR; +} + +static HRESULT ALCmmdevPlayback_openProxy(ALCmmdevPlayback *self) +{ + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + void *ptr; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr); + if(SUCCEEDED(hr)) + { + IMMDeviceEnumerator *Enumerator = ptr; + if(!self->devid) + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator, eRender, eMultimedia, &self->mmdev); + else + hr = IMMDeviceEnumerator_GetDevice(Enumerator, self->devid, &self->mmdev); + IMMDeviceEnumerator_Release(Enumerator); + Enumerator = NULL; + } + if(SUCCEEDED(hr)) + hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr); + if(SUCCEEDED(hr)) + { + self->client = ptr; + get_device_name(self->mmdev, &device->DeviceName); + } + + if(FAILED(hr)) + { + if(self->mmdev) + IMMDevice_Release(self->mmdev); + self->mmdev = NULL; + } + + return hr; +} + + +static void ALCmmdevPlayback_close(ALCmmdevPlayback *self) +{ + ThreadRequest req = { self->MsgEvent, 0 }; + + if(PostThreadMessage(ThreadID, WM_USER_CloseDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + (void)WaitForResponse(&req); + + CloseHandle(self->MsgEvent); + self->MsgEvent = NULL; + + CloseHandle(self->NotifyEvent); + self->NotifyEvent = NULL; + + free(self->devid); + self->devid = NULL; +} + +static void ALCmmdevPlayback_closeProxy(ALCmmdevPlayback *self) +{ + if(self->client) + IAudioClient_Release(self->client); + self->client = NULL; + + if(self->mmdev) + IMMDevice_Release(self->mmdev); + self->mmdev = NULL; +} + + +static ALCboolean ALCmmdevPlayback_reset(ALCmmdevPlayback *self) +{ + ThreadRequest req = { self->MsgEvent, 0 }; + HRESULT hr = E_FAIL; + + if(PostThreadMessage(ThreadID, WM_USER_ResetDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + hr = WaitForResponse(&req); + + return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; +} + +static HRESULT ALCmmdevPlayback_resetProxy(ALCmmdevPlayback *self) +{ + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + EndpointFormFactor formfactor = UnknownFormFactor; + WAVEFORMATEXTENSIBLE OutputType; + WAVEFORMATEX *wfx = NULL; + REFERENCE_TIME min_per, buf_time; + UINT32 buffer_len, min_len; + void *ptr = NULL; + HRESULT hr; + + if(self->client) + IAudioClient_Release(self->client); + self->client = NULL; + + hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr); + if(FAILED(hr)) + { + ERR("Failed to reactivate audio client: 0x%08lx\n", hr); + return hr; + } + self->client = ptr; + + hr = IAudioClient_GetMixFormat(self->client, &wfx); + if(FAILED(hr)) + { + ERR("Failed to get mix format: 0x%08lx\n", hr); + return hr; + } + + if(!MakeExtensible(&OutputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = NULL; + + buf_time = ((REFERENCE_TIME)device->UpdateSize*device->NumUpdates*10000000 + + device->Frequency-1) / device->Frequency; + + if(!(device->Flags&DEVICE_FREQUENCY_REQUEST)) + device->Frequency = OutputType.Format.nSamplesPerSec; + if(!(device->Flags&DEVICE_CHANNELS_REQUEST)) + { + if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) + device->FmtChans = DevFmtMono; + else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) + device->FmtChans = DevFmtStereo; + else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) + device->FmtChans = DevFmtQuad; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) + device->FmtChans = DevFmtX51; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) + device->FmtChans = DevFmtX51Rear; + else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) + device->FmtChans = DevFmtX61; + else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) + device->FmtChans = DevFmtX71; + else + ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); + } + + switch(device->FmtChans) + { + case DevFmtMono: + OutputType.Format.nChannels = 1; + OutputType.dwChannelMask = MONO; + break; + case DevFmtBFormat3D: + device->FmtChans = DevFmtStereo; + /*fall-through*/ + case DevFmtStereo: + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + break; + case DevFmtQuad: + OutputType.Format.nChannels = 4; + OutputType.dwChannelMask = QUAD; + break; + case DevFmtX51: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = X5DOT1; + break; + case DevFmtX51Rear: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = X5DOT1REAR; + break; + case DevFmtX61: + OutputType.Format.nChannels = 7; + OutputType.dwChannelMask = X6DOT1; + break; + case DevFmtX71: + OutputType.Format.nChannels = 8; + OutputType.dwChannelMask = X7DOT1; + break; + } + switch(device->FmtType) + { + case DevFmtByte: + device->FmtType = DevFmtUByte; + /* fall-through */ + case DevFmtUByte: + OutputType.Format.wBitsPerSample = 8; + OutputType.Samples.wValidBitsPerSample = 8; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + OutputType.Format.wBitsPerSample = 16; + OutputType.Samples.wValidBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUInt: + device->FmtType = DevFmtInt; + /* fall-through */ + case DevFmtInt: + OutputType.Format.wBitsPerSample = 32; + OutputType.Samples.wValidBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtFloat: + OutputType.Format.wBitsPerSample = 32; + OutputType.Samples.wValidBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; + } + OutputType.Format.nSamplesPerSec = device->Frequency; + + OutputType.Format.nBlockAlign = OutputType.Format.nChannels * + OutputType.Format.wBitsPerSample / 8; + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * + OutputType.Format.nBlockAlign; + + hr = IAudioClient_IsFormatSupported(self->client, AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + if(FAILED(hr)) + { + ERR("Failed to check format support: 0x%08lx\n", hr); + hr = IAudioClient_GetMixFormat(self->client, &wfx); + } + if(FAILED(hr)) + { + ERR("Failed to find a supported format: 0x%08lx\n", hr); + return hr; + } + + if(wfx != NULL) + { + if(!MakeExtensible(&OutputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = NULL; + + device->Frequency = OutputType.Format.nSamplesPerSec; + if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) + device->FmtChans = DevFmtMono; + else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) + device->FmtChans = DevFmtStereo; + else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) + device->FmtChans = DevFmtQuad; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) + device->FmtChans = DevFmtX51; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR) + device->FmtChans = DevFmtX51Rear; + else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) + device->FmtChans = DevFmtX61; + else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE)) + device->FmtChans = DevFmtX71; + else + { + ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); + device->FmtChans = DevFmtStereo; + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + } + + if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) + { + if(OutputType.Format.wBitsPerSample == 8) + device->FmtType = DevFmtUByte; + else if(OutputType.Format.wBitsPerSample == 16) + device->FmtType = DevFmtShort; + else if(OutputType.Format.wBitsPerSample == 32) + device->FmtType = DevFmtInt; + else + { + device->FmtType = DevFmtShort; + OutputType.Format.wBitsPerSample = 16; + } + } + else if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + { + device->FmtType = DevFmtFloat; + OutputType.Format.wBitsPerSample = 32; + } + else + { + ERR("Unhandled format sub-type\n"); + device->FmtType = DevFmtShort; + OutputType.Format.wBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + } + get_device_formfactor(self->mmdev, &formfactor); + device->IsHeadphones = (device->FmtChans == DevFmtStereo && formfactor == Headphones); + + SetDefaultWFXChannelOrder(device); + + hr = IAudioClient_Initialize(self->client, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + buf_time, 0, &OutputType.Format, NULL); + if(FAILED(hr)) + { + ERR("Failed to initialize audio client: 0x%08lx\n", hr); + return hr; + } + + hr = IAudioClient_GetDevicePeriod(self->client, &min_per, NULL); + if(SUCCEEDED(hr)) + { + min_len = (UINT32)((min_per*device->Frequency + 10000000-1) / 10000000); + /* Find the nearest multiple of the period size to the update size */ + if(min_len < device->UpdateSize) + min_len *= (device->UpdateSize + min_len/2)/min_len; + hr = IAudioClient_GetBufferSize(self->client, &buffer_len); + } + if(FAILED(hr)) + { + ERR("Failed to get audio buffer info: 0x%08lx\n", hr); + return hr; + } + + device->UpdateSize = min_len; + device->NumUpdates = buffer_len / device->UpdateSize; + if(device->NumUpdates <= 1) + { + ERR("Audio client returned buffer_len < period*2; expect break up\n"); + device->NumUpdates = 2; + device->UpdateSize = buffer_len / device->NumUpdates; + } + + hr = IAudioClient_SetEventHandle(self->client, self->NotifyEvent); + if(FAILED(hr)) + { + ERR("Failed to set event handle: 0x%08lx\n", hr); + return hr; + } + + return hr; +} + + +static ALCboolean ALCmmdevPlayback_start(ALCmmdevPlayback *self) +{ + ThreadRequest req = { self->MsgEvent, 0 }; + HRESULT hr = E_FAIL; + + if(PostThreadMessage(ThreadID, WM_USER_StartDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + hr = WaitForResponse(&req); + + return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; +} + +static HRESULT ALCmmdevPlayback_startProxy(ALCmmdevPlayback *self) +{ + HRESULT hr; + void *ptr; + + ResetEvent(self->NotifyEvent); + hr = IAudioClient_Start(self->client); + if(FAILED(hr)) + ERR("Failed to start audio client: 0x%08lx\n", hr); + + if(SUCCEEDED(hr)) + hr = IAudioClient_GetService(self->client, &IID_IAudioRenderClient, &ptr); + if(SUCCEEDED(hr)) + { + self->render = ptr; + self->killNow = 0; + if(althrd_create(&self->thread, ALCmmdevPlayback_mixerProc, self) != althrd_success) + { + if(self->render) + IAudioRenderClient_Release(self->render); + self->render = NULL; + IAudioClient_Stop(self->client); + ERR("Failed to start thread\n"); + hr = E_FAIL; + } + } + + return hr; +} + + +static void ALCmmdevPlayback_stop(ALCmmdevPlayback *self) +{ + ThreadRequest req = { self->MsgEvent, 0 }; + if(PostThreadMessage(ThreadID, WM_USER_StopDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + (void)WaitForResponse(&req); +} + +static void ALCmmdevPlayback_stopProxy(ALCmmdevPlayback *self) +{ + int res; + + if(!self->render) + return; + + self->killNow = 1; + althrd_join(self->thread, &res); + + IAudioRenderClient_Release(self->render); + self->render = NULL; + IAudioClient_Stop(self->client); +} + + +static ALint64 ALCmmdevPlayback_getLatency(ALCmmdevPlayback *self) +{ + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + return (ALint64)self->Padding * 1000000000 / device->Frequency; +} + + +typedef struct ALCmmdevCapture { + DERIVE_FROM_TYPE(ALCbackend); + DERIVE_FROM_TYPE(ALCmmdevProxy); + + WCHAR *devid; + + IMMDevice *mmdev; + IAudioClient *client; + IAudioCaptureClient *capture; + HANDLE NotifyEvent; + + HANDLE MsgEvent; + + RingBuffer *Ring; + + volatile int killNow; + althrd_t thread; +} ALCmmdevCapture; + +static int ALCmmdevCapture_recordProc(void *arg); + +static void ALCmmdevCapture_Construct(ALCmmdevCapture *self, ALCdevice *device); +static void ALCmmdevCapture_Destruct(ALCmmdevCapture *self); +static ALCenum ALCmmdevCapture_open(ALCmmdevCapture *self, const ALCchar *name); +static HRESULT ALCmmdevCapture_openProxy(ALCmmdevCapture *self); +static void ALCmmdevCapture_close(ALCmmdevCapture *self); +static void ALCmmdevCapture_closeProxy(ALCmmdevCapture *self); +static DECLARE_FORWARD(ALCmmdevCapture, ALCbackend, ALCboolean, reset) +static HRESULT ALCmmdevCapture_resetProxy(ALCmmdevCapture *self); +static ALCboolean ALCmmdevCapture_start(ALCmmdevCapture *self); +static HRESULT ALCmmdevCapture_startProxy(ALCmmdevCapture *self); +static void ALCmmdevCapture_stop(ALCmmdevCapture *self); +static void ALCmmdevCapture_stopProxy(ALCmmdevCapture *self); +static ALCenum ALCmmdevCapture_captureSamples(ALCmmdevCapture *self, ALCvoid *buffer, ALCuint samples); +static ALuint ALCmmdevCapture_availableSamples(ALCmmdevCapture *self); +static DECLARE_FORWARD(ALCmmdevCapture, ALCbackend, ALint64, getLatency) +static DECLARE_FORWARD(ALCmmdevCapture, ALCbackend, void, lock) +static DECLARE_FORWARD(ALCmmdevCapture, ALCbackend, void, unlock) +DECLARE_DEFAULT_ALLOCATORS(ALCmmdevCapture) + +DEFINE_ALCMMDEVPROXY_VTABLE(ALCmmdevCapture); +DEFINE_ALCBACKEND_VTABLE(ALCmmdevCapture); + + +static void ALCmmdevCapture_Construct(ALCmmdevCapture *self, ALCdevice *device) +{ + SET_VTABLE2(ALCmmdevCapture, ALCbackend, self); + SET_VTABLE2(ALCmmdevCapture, ALCmmdevProxy, self); + ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device); + ALCmmdevProxy_Construct(STATIC_CAST(ALCmmdevProxy, self)); + + self->devid = NULL; + + self->mmdev = NULL; + self->client = NULL; + self->capture = NULL; + self->NotifyEvent = NULL; + + self->MsgEvent = NULL; + + self->Ring = NULL; + + self->killNow = 0; +} + +static void ALCmmdevCapture_Destruct(ALCmmdevCapture *self) +{ + DestroyRingBuffer(self->Ring); + self->Ring = NULL; + + if(self->NotifyEvent != NULL) + CloseHandle(self->NotifyEvent); + self->NotifyEvent = NULL; + if(self->MsgEvent != NULL) + CloseHandle(self->MsgEvent); + self->MsgEvent = NULL; + + free(self->devid); + self->devid = NULL; + + ALCmmdevProxy_Destruct(STATIC_CAST(ALCmmdevProxy, self)); + ALCbackend_Destruct(STATIC_CAST(ALCbackend, self)); +} + + +FORCE_ALIGN int ALCmmdevCapture_recordProc(void *arg) +{ + ALCmmdevCapture *self = arg; + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + HRESULT hr; + + hr = CoInitialize(NULL); + if(FAILED(hr)) + { + ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr); + V0(device->Backend,lock)(); + aluHandleDisconnect(device); + V0(device->Backend,unlock)(); + return 1; + } + + althrd_setname(althrd_current(), RECORD_THREAD_NAME); + + while(!self->killNow) + { + UINT32 avail; + DWORD res; + + hr = IAudioCaptureClient_GetNextPacketSize(self->capture, &avail); + if(FAILED(hr)) + ERR("Failed to get next packet size: 0x%08lx\n", hr); + else while(avail > 0 && SUCCEEDED(hr)) + { + UINT32 numsamples; + DWORD flags; + BYTE *data; + + hr = IAudioCaptureClient_GetBuffer(self->capture, + &data, &numsamples, &flags, NULL, NULL + ); + if(FAILED(hr)) + { + ERR("Failed to get capture buffer: 0x%08lx\n", hr); + break; + } + + WriteRingBuffer(self->Ring, data, numsamples); + + hr = IAudioCaptureClient_ReleaseBuffer(self->capture, numsamples); + if(FAILED(hr)) + { + ERR("Failed to release capture buffer: 0x%08lx\n", hr); + break; + } + + hr = IAudioCaptureClient_GetNextPacketSize(self->capture, &avail); + if(FAILED(hr)) + ERR("Failed to get next packet size: 0x%08lx\n", hr); + } + + if(FAILED(hr)) + { + V0(device->Backend,lock)(); + aluHandleDisconnect(device); + V0(device->Backend,unlock)(); + break; + } + + res = WaitForSingleObjectEx(self->NotifyEvent, 2000, FALSE); + if(res != WAIT_OBJECT_0) + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + } + + CoUninitialize(); + return 0; +} + + +static ALCenum ALCmmdevCapture_open(ALCmmdevCapture *self, const ALCchar *deviceName) +{ + HRESULT hr = S_OK; + + self->NotifyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + self->MsgEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if(self->NotifyEvent == NULL || self->MsgEvent == NULL) + { + ERR("Failed to create message events: %lu\n", GetLastError()); + hr = E_FAIL; + } + + if(SUCCEEDED(hr)) + { + if(deviceName) + { + const DevMap *iter; + + if(VECTOR_SIZE(CaptureDevices) == 0) + { + ThreadRequest req = { self->MsgEvent, 0 }; + if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, CAPTURE_DEVICE_PROBE)) + (void)WaitForResponse(&req); + } + + hr = E_FAIL; +#define MATCH_NAME(i) (al_string_cmp_cstr((i)->name, deviceName) == 0) + VECTOR_FIND_IF(iter, const DevMap, CaptureDevices, MATCH_NAME); + if(iter == VECTOR_ITER_END(CaptureDevices)) + WARN("Failed to find device name matching \"%s\"\n", deviceName); + else + { + self->devid = strdupW(iter->devid); + hr = S_OK; + } +#undef MATCH_NAME + } + } + + if(SUCCEEDED(hr)) + { + ThreadRequest req = { self->MsgEvent, 0 }; + + hr = E_FAIL; + if(PostThreadMessage(ThreadID, WM_USER_OpenDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + hr = WaitForResponse(&req); + else + ERR("Failed to post thread message: %lu\n", GetLastError()); + } + + if(FAILED(hr)) + { + if(self->NotifyEvent != NULL) + CloseHandle(self->NotifyEvent); + self->NotifyEvent = NULL; + if(self->MsgEvent != NULL) + CloseHandle(self->MsgEvent); + self->MsgEvent = NULL; + + free(self->devid); + self->devid = NULL; + + ERR("Device init failed: 0x%08lx\n", hr); + return ALC_INVALID_VALUE; + } + else + { + ThreadRequest req = { self->MsgEvent, 0 }; + + hr = E_FAIL; + if(PostThreadMessage(ThreadID, WM_USER_ResetDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + hr = WaitForResponse(&req); + else + ERR("Failed to post thread message: %lu\n", GetLastError()); + + if(FAILED(hr)) + { + ALCmmdevCapture_close(self); + if(hr == E_OUTOFMEMORY) + return ALC_OUT_OF_MEMORY; + return ALC_INVALID_VALUE; + } + } + + return ALC_NO_ERROR; +} + +static HRESULT ALCmmdevCapture_openProxy(ALCmmdevCapture *self) +{ + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + void *ptr; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr); + if(SUCCEEDED(hr)) + { + IMMDeviceEnumerator *Enumerator = ptr; + if(!self->devid) + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator, eCapture, eMultimedia, &self->mmdev); + else + hr = IMMDeviceEnumerator_GetDevice(Enumerator, self->devid, &self->mmdev); + IMMDeviceEnumerator_Release(Enumerator); + Enumerator = NULL; + } + if(SUCCEEDED(hr)) + hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr); + if(SUCCEEDED(hr)) + { + self->client = ptr; + get_device_name(self->mmdev, &device->DeviceName); + } + + if(FAILED(hr)) + { + if(self->mmdev) + IMMDevice_Release(self->mmdev); + self->mmdev = NULL; + } + + return hr; +} + + +static void ALCmmdevCapture_close(ALCmmdevCapture *self) +{ + ThreadRequest req = { self->MsgEvent, 0 }; + + if(PostThreadMessage(ThreadID, WM_USER_CloseDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + (void)WaitForResponse(&req); + + DestroyRingBuffer(self->Ring); + self->Ring = NULL; + + CloseHandle(self->MsgEvent); + self->MsgEvent = NULL; + + CloseHandle(self->NotifyEvent); + self->NotifyEvent = NULL; + + free(self->devid); + self->devid = NULL; +} + +static void ALCmmdevCapture_closeProxy(ALCmmdevCapture *self) +{ + if(self->client) + IAudioClient_Release(self->client); + self->client = NULL; + + if(self->mmdev) + IMMDevice_Release(self->mmdev); + self->mmdev = NULL; +} + + +static HRESULT ALCmmdevCapture_resetProxy(ALCmmdevCapture *self) +{ + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + WAVEFORMATEXTENSIBLE OutputType; + REFERENCE_TIME buf_time; + UINT32 buffer_len; + void *ptr = NULL; + HRESULT hr; + + if(self->client) + IAudioClient_Release(self->client); + self->client = NULL; + + hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr); + if(FAILED(hr)) + { + ERR("Failed to reactivate audio client: 0x%08lx\n", hr); + return hr; + } + self->client = ptr; + + buf_time = ((REFERENCE_TIME)device->UpdateSize*device->NumUpdates*10000000 + + device->Frequency-1) / device->Frequency; + + OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + switch(device->FmtChans) + { + case DevFmtMono: + OutputType.Format.nChannels = 1; + OutputType.dwChannelMask = MONO; + break; + case DevFmtStereo: + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + break; + case DevFmtQuad: + OutputType.Format.nChannels = 4; + OutputType.dwChannelMask = QUAD; + break; + case DevFmtX51: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = X5DOT1; + break; + case DevFmtX51Rear: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = X5DOT1REAR; + break; + case DevFmtX61: + OutputType.Format.nChannels = 7; + OutputType.dwChannelMask = X6DOT1; + break; + case DevFmtX71: + OutputType.Format.nChannels = 8; + OutputType.dwChannelMask = X7DOT1; + break; + + case DevFmtBFormat3D: + return E_FAIL; + } + switch(device->FmtType) + { + case DevFmtUByte: + OutputType.Format.wBitsPerSample = 8; + OutputType.Samples.wValidBitsPerSample = 8; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtShort: + OutputType.Format.wBitsPerSample = 16; + OutputType.Samples.wValidBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtInt: + OutputType.Format.wBitsPerSample = 32; + OutputType.Samples.wValidBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtFloat: + OutputType.Format.wBitsPerSample = 32; + OutputType.Samples.wValidBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; + + case DevFmtByte: + case DevFmtUShort: + case DevFmtUInt: + WARN("%s capture samples not supported\n", DevFmtTypeString(device->FmtType)); + return E_FAIL; + } + OutputType.Format.nSamplesPerSec = device->Frequency; + + OutputType.Format.nBlockAlign = OutputType.Format.nChannels * + OutputType.Format.wBitsPerSample / 8; + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * + OutputType.Format.nBlockAlign; + + hr = IAudioClient_IsFormatSupported(self->client, + AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, NULL + ); + if(FAILED(hr)) + { + ERR("Failed to check format support: 0x%08lx\n", hr); + return hr; + } + + hr = IAudioClient_Initialize(self->client, + AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + buf_time, 0, &OutputType.Format, NULL + ); + if(FAILED(hr)) + { + ERR("Failed to initialize audio client: 0x%08lx\n", hr); + return hr; + } + + hr = IAudioClient_GetBufferSize(self->client, &buffer_len); + if(FAILED(hr)) + { + ERR("Failed to get buffer size: 0x%08lx\n", hr); + return hr; + } + + buffer_len = maxu(device->UpdateSize*device->NumUpdates, buffer_len); + self->Ring = CreateRingBuffer(OutputType.Format.nBlockAlign, buffer_len); + if(!self->Ring) + { + ERR("Failed to allocate capture ring buffer\n"); + return E_OUTOFMEMORY; + } + + hr = IAudioClient_SetEventHandle(self->client, self->NotifyEvent); + if(FAILED(hr)) + { + ERR("Failed to set event handle: 0x%08lx\n", hr); + return hr; + } + + return hr; +} + + +static ALCboolean ALCmmdevCapture_start(ALCmmdevCapture *self) +{ + ThreadRequest req = { self->MsgEvent, 0 }; + HRESULT hr = E_FAIL; + + if(PostThreadMessage(ThreadID, WM_USER_StartDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + hr = WaitForResponse(&req); + + return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; +} + +static HRESULT ALCmmdevCapture_startProxy(ALCmmdevCapture *self) +{ + HRESULT hr; + void *ptr; + + ResetEvent(self->NotifyEvent); + hr = IAudioClient_Start(self->client); + if(FAILED(hr)) + { + ERR("Failed to start audio client: 0x%08lx\n", hr); + return hr; + } + + hr = IAudioClient_GetService(self->client, &IID_IAudioCaptureClient, &ptr); + if(SUCCEEDED(hr)) + { + self->capture = ptr; + self->killNow = 0; + if(althrd_create(&self->thread, ALCmmdevCapture_recordProc, self) != althrd_success) + { + ERR("Failed to start thread\n"); + IAudioCaptureClient_Release(self->capture); + self->capture = NULL; + hr = E_FAIL; + } + } + + if(FAILED(hr)) + { + IAudioClient_Stop(self->client); + IAudioClient_Reset(self->client); + } + + return hr; +} + + +static void ALCmmdevCapture_stop(ALCmmdevCapture *self) +{ + ThreadRequest req = { self->MsgEvent, 0 }; + if(PostThreadMessage(ThreadID, WM_USER_StopDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCmmdevProxy, self))) + (void)WaitForResponse(&req); +} + +static void ALCmmdevCapture_stopProxy(ALCmmdevCapture *self) +{ + int res; + + if(!self->capture) + return; + + self->killNow = 1; + althrd_join(self->thread, &res); + + IAudioCaptureClient_Release(self->capture); + self->capture = NULL; + IAudioClient_Stop(self->client); + IAudioClient_Reset(self->client); +} + + +ALuint ALCmmdevCapture_availableSamples(ALCmmdevCapture *self) +{ + return RingBufferSize(self->Ring); +} + +ALCenum ALCmmdevCapture_captureSamples(ALCmmdevCapture *self, ALCvoid *buffer, ALCuint samples) +{ + if(ALCmmdevCapture_availableSamples(self) < samples) + return ALC_INVALID_VALUE; + ReadRingBuffer(self->Ring, buffer, samples); + return ALC_NO_ERROR; +} + + +static inline void AppendAllDevicesList2(const DevMap *entry) +{ AppendAllDevicesList(al_string_get_cstr(entry->name)); } +static inline void AppendCaptureDeviceList2(const DevMap *entry) +{ AppendCaptureDeviceList(al_string_get_cstr(entry->name)); } + +typedef struct ALCmmdevBackendFactory { + DERIVE_FROM_TYPE(ALCbackendFactory); +} ALCmmdevBackendFactory; +#define ALCMMDEVBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCmmdevBackendFactory, ALCbackendFactory) } } + +static ALCboolean ALCmmdevBackendFactory_init(ALCmmdevBackendFactory *self); +static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory *self); +static ALCboolean ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory *self, ALCbackend_Type type); +static void ALCmmdevBackendFactory_probe(ALCmmdevBackendFactory *self, enum DevProbe type); +static ALCbackend* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory *self, ALCdevice *device, ALCbackend_Type type); + +DEFINE_ALCBACKENDFACTORY_VTABLE(ALCmmdevBackendFactory); + + +static BOOL MMDevApiLoad(void) +{ + static HRESULT InitResult; + if(!ThreadHdl) + { + ThreadRequest req; + InitResult = E_FAIL; + + req.FinishedEvt = CreateEvent(NULL, FALSE, FALSE, NULL); + if(req.FinishedEvt == NULL) + ERR("Failed to create event: %lu\n", GetLastError()); + else + { + ThreadHdl = CreateThread(NULL, 0, ALCmmdevProxy_messageHandler, &req, 0, &ThreadID); + if(ThreadHdl != NULL) + InitResult = WaitForResponse(&req); + CloseHandle(req.FinishedEvt); + } + } + return SUCCEEDED(InitResult); +} + +static ALCboolean ALCmmdevBackendFactory_init(ALCmmdevBackendFactory* UNUSED(self)) +{ + VECTOR_INIT(PlaybackDevices); + VECTOR_INIT(CaptureDevices); + + if(!MMDevApiLoad()) + return ALC_FALSE; + return ALC_TRUE; +} + +static void ALCmmdevBackendFactory_deinit(ALCmmdevBackendFactory* UNUSED(self)) +{ + clear_devlist(&PlaybackDevices); + VECTOR_DEINIT(PlaybackDevices); + + clear_devlist(&CaptureDevices); + VECTOR_DEINIT(CaptureDevices); + + if(ThreadHdl) + { + TRACE("Sending WM_QUIT to Thread %04lx\n", ThreadID); + PostThreadMessage(ThreadID, WM_QUIT, 0, 0); + CloseHandle(ThreadHdl); + ThreadHdl = NULL; + } +} + +static ALCboolean ALCmmdevBackendFactory_querySupport(ALCmmdevBackendFactory* UNUSED(self), ALCbackend_Type type) +{ + if(type == ALCbackend_Playback/* || type == ALCbackend_Capture*/) + return ALC_TRUE; + return ALC_FALSE; +} + +static void ALCmmdevBackendFactory_probe(ALCmmdevBackendFactory* UNUSED(self), enum DevProbe type) +{ + ThreadRequest req = { NULL, 0 }; + + req.FinishedEvt = CreateEvent(NULL, FALSE, FALSE, NULL); + if(req.FinishedEvt == NULL) + ERR("Failed to create event: %lu\n", GetLastError()); + else + { + HRESULT hr = E_FAIL; + if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, type)) + hr = WaitForResponse(&req); + if(SUCCEEDED(hr)) switch(type) + { + case ALL_DEVICE_PROBE: + VECTOR_FOR_EACH(const DevMap, PlaybackDevices, AppendAllDevicesList2); + break; + + case CAPTURE_DEVICE_PROBE: + VECTOR_FOR_EACH(const DevMap, CaptureDevices, AppendCaptureDeviceList2); + break; + } + CloseHandle(req.FinishedEvt); + req.FinishedEvt = NULL; + } +} + +static ALCbackend* ALCmmdevBackendFactory_createBackend(ALCmmdevBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type) +{ + if(type == ALCbackend_Playback) + { + ALCmmdevPlayback *backend; + + backend = ALCmmdevPlayback_New(sizeof(*backend)); + if(!backend) return NULL; + memset(backend, 0, sizeof(*backend)); + + ALCmmdevPlayback_Construct(backend, device); + + return STATIC_CAST(ALCbackend, backend); + } + if(type == ALCbackend_Capture) + { + ALCmmdevCapture *backend; + + backend = ALCmmdevCapture_New(sizeof(*backend)); + if(!backend) return NULL; + memset(backend, 0, sizeof(*backend)); + + ALCmmdevCapture_Construct(backend, device); + + return STATIC_CAST(ALCbackend, backend); + } + + return NULL; +} + + +ALCbackendFactory *ALCmmdevBackendFactory_getFactory(void) +{ + static ALCmmdevBackendFactory factory = ALCMMDEVBACKENDFACTORY_INITIALIZER; + return STATIC_CAST(ALCbackendFactory, &factory); +} diff --git a/Telegram/_openal_patch/Alc/backends/winmm.c b/Telegram/_openal_patch/Alc/backends/winmm.c index 81ce905efb..77212c2b3c 100644 --- a/Telegram/_openal_patch/Alc/backends/winmm.c +++ b/Telegram/_openal_patch/Alc/backends/winmm.c @@ -31,30 +31,13 @@ #include "alu.h" #include "threads.h" +#include "backends/base.h" + #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 #endif -typedef struct { - // MMSYSTEM Device - volatile ALboolean killNow; - althrd_t thread; - - RefCount WaveBuffersCommitted; - WAVEHDR WaveBuffer[4]; - - union { - HWAVEIN In; - HWAVEOUT Out; - } WaveHandle; - - WAVEFORMATEX Format; - - RingBuffer *Ring; -} WinMMData; - - TYPEDEF_VECTOR(al_string, vector_al_string) static vector_al_string PlaybackDevices; static vector_al_string CaptureDevices; @@ -155,111 +138,117 @@ static void ProbeCaptureDevices(void) } -/* - WaveOutProc +typedef struct ALCwinmmPlayback { + DERIVE_FROM_TYPE(ALCbackend); - Posts a message to 'PlaybackThreadProc' everytime a WaveOut Buffer is completed and - returns to the application (for more data) -*/ -static void CALLBACK WaveOutProc(HWAVEOUT UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2)) + RefCount WaveBuffersCommitted; + WAVEHDR WaveBuffer[4]; + + HWAVEOUT OutHdl; + + WAVEFORMATEX Format; + + volatile ALboolean killNow; + althrd_t thread; +} ALCwinmmPlayback; + +static void ALCwinmmPlayback_Construct(ALCwinmmPlayback *self, ALCdevice *device); +static void ALCwinmmPlayback_Destruct(ALCwinmmPlayback *self); + +static void CALLBACK ALCwinmmPlayback_waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); +static int ALCwinmmPlayback_mixerProc(void *arg); + +static ALCenum ALCwinmmPlayback_open(ALCwinmmPlayback *self, const ALCchar *name); +static void ALCwinmmPlayback_close(ALCwinmmPlayback *self); +static ALCboolean ALCwinmmPlayback_reset(ALCwinmmPlayback *self); +static ALCboolean ALCwinmmPlayback_start(ALCwinmmPlayback *self); +static void ALCwinmmPlayback_stop(ALCwinmmPlayback *self); +static DECLARE_FORWARD2(ALCwinmmPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint) +static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, ALCuint, availableSamples) +static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, ALint64, getLatency) +static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, void, lock) +static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, void, unlock) +DECLARE_DEFAULT_ALLOCATORS(ALCwinmmPlayback) + +DEFINE_ALCBACKEND_VTABLE(ALCwinmmPlayback); + + +static void ALCwinmmPlayback_Construct(ALCwinmmPlayback *self, ALCdevice *device) { - ALCdevice *Device = (ALCdevice*)instance; - WinMMData *data = Device->ExtraData; + ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device); + SET_VTABLE2(ALCwinmmPlayback, ALCbackend, self); + + InitRef(&self->WaveBuffersCommitted, 0); + self->OutHdl = NULL; + + self->killNow = AL_TRUE; +} + +static void ALCwinmmPlayback_Destruct(ALCwinmmPlayback *self) +{ + if(self->OutHdl) + waveOutClose(self->OutHdl); + self->OutHdl = 0; + + ALCbackend_Destruct(STATIC_CAST(ALCbackend, self)); +} + + +/* ALCwinmmPlayback_waveOutProc + * + * Posts a message to 'ALCwinmmPlayback_mixerProc' everytime a WaveOut Buffer + * is completed and returns to the application (for more data) + */ +static void CALLBACK ALCwinmmPlayback_waveOutProc(HWAVEOUT UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2)) +{ + ALCwinmmPlayback *self = (ALCwinmmPlayback*)instance; if(msg != WOM_DONE) return; - DecrementRef(&data->WaveBuffersCommitted); - PostThreadMessage(data->thread, msg, 0, param1); + DecrementRef(&self->WaveBuffersCommitted); + PostThreadMessage(self->thread, msg, 0, param1); } -FORCE_ALIGN static int PlaybackThreadProc(void *arg) +FORCE_ALIGN static int ALCwinmmPlayback_mixerProc(void *arg) { - ALCdevice *Device = (ALCdevice*)arg; - WinMMData *data = Device->ExtraData; + ALCwinmmPlayback *self = arg; + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; WAVEHDR *WaveHdr; MSG msg; SetRTPriority(); althrd_setname(althrd_current(), MIXER_THREAD_NAME); - while(GetMessage(&msg, NULL, 0, 0)) + if (!self->killNow) while (GetMessage(&msg, NULL, 0, 0)) { if(msg.message != WOM_DONE) continue; - if(data->killNow) + if(self->killNow) { - if(ReadRef(&data->WaveBuffersCommitted) == 0) + if(ReadRef(&self->WaveBuffersCommitted) == 0) break; continue; } WaveHdr = ((WAVEHDR*)msg.lParam); - aluMixData(Device, WaveHdr->lpData, WaveHdr->dwBufferLength / - data->Format.nBlockAlign); + aluMixData(device, WaveHdr->lpData, WaveHdr->dwBufferLength / + self->Format.nBlockAlign); // Send buffer back to play more data - waveOutWrite(data->WaveHandle.Out, WaveHdr, sizeof(WAVEHDR)); - IncrementRef(&data->WaveBuffersCommitted); - } - - return 0; -} - -/* - WaveInProc - - Posts a message to 'CaptureThreadProc' everytime a WaveIn Buffer is completed and - returns to the application (with more data) -*/ -static void CALLBACK WaveInProc(HWAVEIN UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2)) -{ - ALCdevice *Device = (ALCdevice*)instance; - WinMMData *data = Device->ExtraData; - - if(msg != WIM_DATA) - return; - - DecrementRef(&data->WaveBuffersCommitted); - PostThreadMessage(data->thread, msg, 0, param1); -} - -static int CaptureThreadProc(void *arg) -{ - ALCdevice *Device = (ALCdevice*)arg; - WinMMData *data = Device->ExtraData; - WAVEHDR *WaveHdr; - MSG msg; - - althrd_setname(althrd_current(), "alsoft-record"); - - if (!data->killNow) while(GetMessage(&msg, NULL, 0, 0)) - { - if(msg.message != WIM_DATA) - continue; - /* Don't wait for other buffers to finish before quitting. We're - * closing so we don't need them. */ - if(data->killNow) - break; - - WaveHdr = ((WAVEHDR*)msg.lParam); - WriteRingBuffer(data->Ring, (ALubyte*)WaveHdr->lpData, - WaveHdr->dwBytesRecorded/data->Format.nBlockAlign); - - // Send buffer back to capture more data - waveInAddBuffer(data->WaveHandle.In, WaveHdr, sizeof(WAVEHDR)); - IncrementRef(&data->WaveBuffersCommitted); + waveOutWrite(self->OutHdl, WaveHdr, sizeof(WAVEHDR)); + IncrementRef(&self->WaveBuffersCommitted); } return 0; } -static ALCenum WinMMOpenPlayback(ALCdevice *Device, const ALCchar *deviceName) +static ALCenum ALCwinmmPlayback_open(ALCwinmmPlayback *self, const ALCchar *deviceName) { - WinMMData *data = NULL; - const al_string *iter, *end; + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + const al_string *iter; UINT DeviceID; MMRESULT res; @@ -267,129 +256,109 @@ static ALCenum WinMMOpenPlayback(ALCdevice *Device, const ALCchar *deviceName) ProbePlaybackDevices(); // Find the Device ID matching the deviceName if valid - iter = VECTOR_ITER_BEGIN(PlaybackDevices); - end = VECTOR_ITER_END(PlaybackDevices); - for(;iter != end;iter++) - { - if(!al_string_empty(*iter) && - (!deviceName || al_string_cmp_cstr(*iter, deviceName) == 0)) - { - DeviceID = (UINT)(iter - VECTOR_ITER_BEGIN(PlaybackDevices)); - break; - } - } - if(iter == end) +#define MATCH_DEVNAME(iter) (!al_string_empty(*(iter)) && \ + (!deviceName || al_string_cmp_cstr(*(iter), deviceName) == 0)) + VECTOR_FIND_IF(iter, const al_string, PlaybackDevices, MATCH_DEVNAME); + if(iter == VECTOR_ITER_END(PlaybackDevices)) return ALC_INVALID_VALUE; +#undef MATCH_DEVNAME - data = calloc(1, sizeof(*data)); - if(!data) - return ALC_OUT_OF_MEMORY; - Device->ExtraData = data; + DeviceID = (UINT)(iter - VECTOR_ITER_BEGIN(PlaybackDevices)); retry_open: - memset(&data->Format, 0, sizeof(WAVEFORMATEX)); - if(Device->FmtType == DevFmtFloat) + memset(&self->Format, 0, sizeof(WAVEFORMATEX)); + if(device->FmtType == DevFmtFloat) { - data->Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - data->Format.wBitsPerSample = 32; + self->Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + self->Format.wBitsPerSample = 32; } else { - data->Format.wFormatTag = WAVE_FORMAT_PCM; - if(Device->FmtType == DevFmtUByte || Device->FmtType == DevFmtByte) - data->Format.wBitsPerSample = 8; + self->Format.wFormatTag = WAVE_FORMAT_PCM; + if(device->FmtType == DevFmtUByte || device->FmtType == DevFmtByte) + self->Format.wBitsPerSample = 8; else - data->Format.wBitsPerSample = 16; + self->Format.wBitsPerSample = 16; } - data->Format.nChannels = ((Device->FmtChans == DevFmtMono) ? 1 : 2); - data->Format.nBlockAlign = data->Format.wBitsPerSample * - data->Format.nChannels / 8; - data->Format.nSamplesPerSec = Device->Frequency; - data->Format.nAvgBytesPerSec = data->Format.nSamplesPerSec * - data->Format.nBlockAlign; - data->Format.cbSize = 0; + self->Format.nChannels = ((device->FmtChans == DevFmtMono) ? 1 : 2); + self->Format.nBlockAlign = self->Format.wBitsPerSample * + self->Format.nChannels / 8; + self->Format.nSamplesPerSec = device->Frequency; + self->Format.nAvgBytesPerSec = self->Format.nSamplesPerSec * + self->Format.nBlockAlign; + self->Format.cbSize = 0; - if((res=waveOutOpen(&data->WaveHandle.Out, DeviceID, &data->Format, (DWORD_PTR)&WaveOutProc, (DWORD_PTR)Device, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) + if((res=waveOutOpen(&self->OutHdl, DeviceID, &self->Format, (DWORD_PTR)&ALCwinmmPlayback_waveOutProc, (DWORD_PTR)self, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) { - if(Device->FmtType == DevFmtFloat) + if(device->FmtType == DevFmtFloat) { - Device->FmtType = DevFmtShort; + device->FmtType = DevFmtShort; goto retry_open; } ERR("waveOutOpen failed: %u\n", res); goto failure; } - al_string_copy(&Device->DeviceName, VECTOR_ELEM(PlaybackDevices, DeviceID)); + al_string_copy(&device->DeviceName, VECTOR_ELEM(PlaybackDevices, DeviceID)); return ALC_NO_ERROR; failure: - if(data->WaveHandle.Out) - waveOutClose(data->WaveHandle.Out); + if(self->OutHdl) + waveOutClose(self->OutHdl); + self->OutHdl = NULL; - free(data); - Device->ExtraData = NULL; return ALC_INVALID_VALUE; } -static void WinMMClosePlayback(ALCdevice *device) +static void ALCwinmmPlayback_close(ALCwinmmPlayback* UNUSED(self)) +{ } + +static ALCboolean ALCwinmmPlayback_reset(ALCwinmmPlayback *self) { - WinMMData *data = (WinMMData*)device->ExtraData; - - // Close the Wave device - waveOutClose(data->WaveHandle.Out); - data->WaveHandle.Out = 0; - - free(data); - device->ExtraData = NULL; -} - -static ALCboolean WinMMResetPlayback(ALCdevice *device) -{ - WinMMData *data = (WinMMData*)device->ExtraData; + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; device->UpdateSize = (ALuint)((ALuint64)device->UpdateSize * - data->Format.nSamplesPerSec / + self->Format.nSamplesPerSec / device->Frequency); device->UpdateSize = (device->UpdateSize*device->NumUpdates + 3) / 4; device->NumUpdates = 4; - device->Frequency = data->Format.nSamplesPerSec; + device->Frequency = self->Format.nSamplesPerSec; - if(data->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) + if(self->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { - if(data->Format.wBitsPerSample == 32) + if(self->Format.wBitsPerSample == 32) device->FmtType = DevFmtFloat; else { - ERR("Unhandled IEEE float sample depth: %d\n", data->Format.wBitsPerSample); + ERR("Unhandled IEEE float sample depth: %d\n", self->Format.wBitsPerSample); return ALC_FALSE; } } - else if(data->Format.wFormatTag == WAVE_FORMAT_PCM) + else if(self->Format.wFormatTag == WAVE_FORMAT_PCM) { - if(data->Format.wBitsPerSample == 16) + if(self->Format.wBitsPerSample == 16) device->FmtType = DevFmtShort; - else if(data->Format.wBitsPerSample == 8) + else if(self->Format.wBitsPerSample == 8) device->FmtType = DevFmtUByte; else { - ERR("Unhandled PCM sample depth: %d\n", data->Format.wBitsPerSample); + ERR("Unhandled PCM sample depth: %d\n", self->Format.wBitsPerSample); return ALC_FALSE; } } else { - ERR("Unhandled format tag: 0x%04x\n", data->Format.wFormatTag); + ERR("Unhandled format tag: 0x%04x\n", self->Format.wFormatTag); return ALC_FALSE; } - if(data->Format.nChannels == 2) + if(self->Format.nChannels == 2) device->FmtChans = DevFmtStereo; - else if(data->Format.nChannels == 1) + else if(self->Format.nChannels == 1) device->FmtChans = DevFmtMono; else { - ERR("Unhandled channel count: %d\n", data->Format.nChannels); + ERR("Unhandled channel count: %d\n", self->Format.nChannels); return ALC_FALSE; } SetDefaultWFXChannelOrder(device); @@ -397,18 +366,18 @@ static ALCboolean WinMMResetPlayback(ALCdevice *device) return ALC_TRUE; } -static ALCboolean WinMMStartPlayback(ALCdevice *device) +static ALCboolean ALCwinmmPlayback_start(ALCwinmmPlayback *self) { - WinMMData *data = (WinMMData*)device->ExtraData; + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; ALbyte *BufferData; ALint BufferSize; ALuint i; - data->killNow = AL_FALSE; - if(althrd_create(&data->thread, PlaybackThreadProc, device) != althrd_success) + self->killNow = AL_FALSE; + if(althrd_create(&self->thread, ALCwinmmPlayback_mixerProc, self) != althrd_success) return ALC_FALSE; - InitRef(&data->WaveBuffersCommitted, 0); + InitRef(&self->WaveBuffersCommitted, 0); // Create 4 Buffers BufferSize = device->UpdateSize*device->NumUpdates / 4; @@ -417,49 +386,153 @@ static ALCboolean WinMMStartPlayback(ALCdevice *device) BufferData = calloc(4, BufferSize); for(i = 0;i < 4;i++) { - memset(&data->WaveBuffer[i], 0, sizeof(WAVEHDR)); - data->WaveBuffer[i].dwBufferLength = BufferSize; - data->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData : - (data->WaveBuffer[i-1].lpData + - data->WaveBuffer[i-1].dwBufferLength)); - waveOutPrepareHeader(data->WaveHandle.Out, &data->WaveBuffer[i], sizeof(WAVEHDR)); - waveOutWrite(data->WaveHandle.Out, &data->WaveBuffer[i], sizeof(WAVEHDR)); - IncrementRef(&data->WaveBuffersCommitted); + memset(&self->WaveBuffer[i], 0, sizeof(WAVEHDR)); + self->WaveBuffer[i].dwBufferLength = BufferSize; + self->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData : + (self->WaveBuffer[i-1].lpData + + self->WaveBuffer[i-1].dwBufferLength)); + waveOutPrepareHeader(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR)); + waveOutWrite(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR)); + IncrementRef(&self->WaveBuffersCommitted); } return ALC_TRUE; } -static void WinMMStopPlayback(ALCdevice *device) +static void ALCwinmmPlayback_stop(ALCwinmmPlayback *self) { - WinMMData *data = (WinMMData*)device->ExtraData; void *buffer = NULL; int i; - if(data->killNow) + if(self->killNow) return; // Set flag to stop processing headers - data->killNow = AL_TRUE; - althrd_join(data->thread, &i); + self->killNow = AL_TRUE; + althrd_join(self->thread, &i); // Release the wave buffers for(i = 0;i < 4;i++) { - waveOutUnprepareHeader(data->WaveHandle.Out, &data->WaveBuffer[i], sizeof(WAVEHDR)); - if(i == 0) buffer = data->WaveBuffer[i].lpData; - data->WaveBuffer[i].lpData = NULL; + waveOutUnprepareHeader(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR)); + if(i == 0) buffer = self->WaveBuffer[i].lpData; + self->WaveBuffer[i].lpData = NULL; } free(buffer); } -static ALCenum WinMMOpenCapture(ALCdevice *Device, const ALCchar *deviceName) + +typedef struct ALCwinmmCapture { + DERIVE_FROM_TYPE(ALCbackend); + + RefCount WaveBuffersCommitted; + WAVEHDR WaveBuffer[4]; + + HWAVEIN InHdl; + + RingBuffer *Ring; + + WAVEFORMATEX Format; + + volatile ALboolean killNow; + althrd_t thread; +} ALCwinmmCapture; + +static void ALCwinmmCapture_Construct(ALCwinmmCapture *self, ALCdevice *device); +static void ALCwinmmCapture_Destruct(ALCwinmmCapture *self); + +static void CALLBACK ALCwinmmCapture_waveInProc(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); +static int ALCwinmmCapture_captureProc(void *arg); + +static ALCenum ALCwinmmCapture_open(ALCwinmmCapture *self, const ALCchar *name); +static void ALCwinmmCapture_close(ALCwinmmCapture *self); +static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, ALCboolean, reset) +static ALCboolean ALCwinmmCapture_start(ALCwinmmCapture *self); +static void ALCwinmmCapture_stop(ALCwinmmCapture *self); +static ALCenum ALCwinmmCapture_captureSamples(ALCwinmmCapture *self, ALCvoid *buffer, ALCuint samples); +static ALCuint ALCwinmmCapture_availableSamples(ALCwinmmCapture *self); +static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, ALint64, getLatency) +static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, void, lock) +static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, void, unlock) +DECLARE_DEFAULT_ALLOCATORS(ALCwinmmCapture) + +DEFINE_ALCBACKEND_VTABLE(ALCwinmmCapture); + + +static void ALCwinmmCapture_Construct(ALCwinmmCapture *self, ALCdevice *device) { - const al_string *iter, *end; + ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device); + SET_VTABLE2(ALCwinmmCapture, ALCbackend, self); + + InitRef(&self->WaveBuffersCommitted, 0); + self->InHdl = NULL; + + self->killNow = AL_TRUE; +} + +static void ALCwinmmCapture_Destruct(ALCwinmmCapture *self) +{ + if(self->InHdl) + waveInClose(self->InHdl); + self->InHdl = 0; + + ALCbackend_Destruct(STATIC_CAST(ALCbackend, self)); +} + + +/* ALCwinmmCapture_waveInProc + * + * Posts a message to 'ALCwinmmCapture_captureProc' everytime a WaveIn Buffer + * is completed and returns to the application (with more data). + */ +static void CALLBACK ALCwinmmCapture_waveInProc(HWAVEIN UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2)) +{ + ALCwinmmCapture *self = (ALCwinmmCapture*)instance; + + if(msg != WIM_DATA) + return; + + DecrementRef(&self->WaveBuffersCommitted); + PostThreadMessage(self->thread, msg, 0, param1); +} + +static int ALCwinmmCapture_captureProc(void *arg) +{ + ALCwinmmCapture *self = arg; + WAVEHDR *WaveHdr; + MSG msg; + + althrd_setname(althrd_current(), RECORD_THREAD_NAME); + + if (!self->killNow) while(GetMessage(&msg, NULL, 0, 0)) + { + if(msg.message != WIM_DATA) + continue; + /* Don't wait for other buffers to finish before quitting. We're + * closing so we don't need them. */ + if(self->killNow) + break; + + WaveHdr = ((WAVEHDR*)msg.lParam); + WriteRingBuffer(self->Ring, (ALubyte*)WaveHdr->lpData, + WaveHdr->dwBytesRecorded/self->Format.nBlockAlign); + + // Send buffer back to capture more data + waveInAddBuffer(self->InHdl, WaveHdr, sizeof(WAVEHDR)); + IncrementRef(&self->WaveBuffersCommitted); + } + + return 0; +} + + +static ALCenum ALCwinmmCapture_open(ALCwinmmCapture *self, const ALCchar *name) +{ + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + const al_string *iter; ALbyte *BufferData = NULL; DWORD CapturedDataSize; - WinMMData *data = NULL; ALint BufferSize; UINT DeviceID; MMRESULT res; @@ -469,21 +542,15 @@ static ALCenum WinMMOpenCapture(ALCdevice *Device, const ALCchar *deviceName) ProbeCaptureDevices(); // Find the Device ID matching the deviceName if valid - iter = VECTOR_ITER_BEGIN(CaptureDevices); - end = VECTOR_ITER_END(CaptureDevices); - for(;iter != end;iter++) - { - if(!al_string_empty(*iter) && - (!deviceName || al_string_cmp_cstr(*iter, deviceName) == 0)) - { - DeviceID = (UINT)(iter - VECTOR_ITER_BEGIN(CaptureDevices)); - break; - } - } - if(iter == end) +#define MATCH_DEVNAME(iter) (!al_string_empty(*(iter)) && (!name || al_string_cmp_cstr(*iter, name) == 0)) + VECTOR_FIND_IF(iter, const al_string, CaptureDevices, MATCH_DEVNAME); + if(iter == VECTOR_ITER_END(CaptureDevices)) return ALC_INVALID_VALUE; +#undef MATCH_DEVNAME - switch(Device->FmtChans) + DeviceID = (UINT)(iter - VECTOR_ITER_BEGIN(CaptureDevices)); + + switch(device->FmtChans) { case DevFmtMono: case DevFmtStereo: @@ -491,13 +558,14 @@ static ALCenum WinMMOpenCapture(ALCdevice *Device, const ALCchar *deviceName) case DevFmtQuad: case DevFmtX51: - case DevFmtX51Side: + case DevFmtX51Rear: case DevFmtX61: case DevFmtX71: + case DevFmtBFormat3D: return ALC_INVALID_ENUM; } - switch(Device->FmtType) + switch(device->FmtType) { case DevFmtUByte: case DevFmtShort: @@ -511,147 +579,134 @@ static ALCenum WinMMOpenCapture(ALCdevice *Device, const ALCchar *deviceName) return ALC_INVALID_ENUM; } - data = calloc(1, sizeof(*data)); - if(!data) - return ALC_OUT_OF_MEMORY; - Device->ExtraData = data; - - memset(&data->Format, 0, sizeof(WAVEFORMATEX)); - data->Format.wFormatTag = ((Device->FmtType == DevFmtFloat) ? + memset(&self->Format, 0, sizeof(WAVEFORMATEX)); + self->Format.wFormatTag = ((device->FmtType == DevFmtFloat) ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM); - data->Format.nChannels = ChannelsFromDevFmt(Device->FmtChans); - data->Format.wBitsPerSample = BytesFromDevFmt(Device->FmtType) * 8; - data->Format.nBlockAlign = data->Format.wBitsPerSample * - data->Format.nChannels / 8; - data->Format.nSamplesPerSec = Device->Frequency; - data->Format.nAvgBytesPerSec = data->Format.nSamplesPerSec * - data->Format.nBlockAlign; - data->Format.cbSize = 0; + self->Format.nChannels = ChannelsFromDevFmt(device->FmtChans); + self->Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8; + self->Format.nBlockAlign = self->Format.wBitsPerSample * + self->Format.nChannels / 8; + self->Format.nSamplesPerSec = device->Frequency; + self->Format.nAvgBytesPerSec = self->Format.nSamplesPerSec * + self->Format.nBlockAlign; + self->Format.cbSize = 0; - if((res=waveInOpen(&data->WaveHandle.In, DeviceID, &data->Format, (DWORD_PTR)&WaveInProc, (DWORD_PTR)Device, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) + if((res=waveInOpen(&self->InHdl, DeviceID, &self->Format, (DWORD_PTR)&ALCwinmmCapture_waveInProc, (DWORD_PTR)self, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) { ERR("waveInOpen failed: %u\n", res); goto failure; } // Allocate circular memory buffer for the captured audio - CapturedDataSize = Device->UpdateSize*Device->NumUpdates; + CapturedDataSize = device->UpdateSize*device->NumUpdates; // Make sure circular buffer is at least 100ms in size - if(CapturedDataSize < (data->Format.nSamplesPerSec / 10)) - CapturedDataSize = data->Format.nSamplesPerSec / 10; + if(CapturedDataSize < (self->Format.nSamplesPerSec / 10)) + CapturedDataSize = self->Format.nSamplesPerSec / 10; - data->Ring = CreateRingBuffer(data->Format.nBlockAlign, CapturedDataSize); - if(!data->Ring) - goto failure; + self->Ring = CreateRingBuffer(self->Format.nBlockAlign, CapturedDataSize); + if(!self->Ring) goto failure; - InitRef(&data->WaveBuffersCommitted, 0); + InitRef(&self->WaveBuffersCommitted, 0); // Create 4 Buffers of 50ms each - BufferSize = data->Format.nAvgBytesPerSec / 20; - BufferSize -= (BufferSize % data->Format.nBlockAlign); + BufferSize = self->Format.nAvgBytesPerSec / 20; + BufferSize -= (BufferSize % self->Format.nBlockAlign); BufferData = calloc(4, BufferSize); - if(!BufferData) - goto failure; + if(!BufferData) goto failure; for(i = 0;i < 4;i++) { - memset(&data->WaveBuffer[i], 0, sizeof(WAVEHDR)); - data->WaveBuffer[i].dwBufferLength = BufferSize; - data->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData : - (data->WaveBuffer[i-1].lpData + - data->WaveBuffer[i-1].dwBufferLength)); - data->WaveBuffer[i].dwFlags = 0; - data->WaveBuffer[i].dwLoops = 0; - waveInPrepareHeader(data->WaveHandle.In, &data->WaveBuffer[i], sizeof(WAVEHDR)); - waveInAddBuffer(data->WaveHandle.In, &data->WaveBuffer[i], sizeof(WAVEHDR)); - IncrementRef(&data->WaveBuffersCommitted); + memset(&self->WaveBuffer[i], 0, sizeof(WAVEHDR)); + self->WaveBuffer[i].dwBufferLength = BufferSize; + self->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData : + (self->WaveBuffer[i-1].lpData + + self->WaveBuffer[i-1].dwBufferLength)); + self->WaveBuffer[i].dwFlags = 0; + self->WaveBuffer[i].dwLoops = 0; + waveInPrepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR)); + waveInAddBuffer(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR)); + IncrementRef(&self->WaveBuffersCommitted); } - if(althrd_create(&data->thread, CaptureThreadProc, Device) != althrd_success) + self->killNow = AL_FALSE; + if(althrd_create(&self->thread, ALCwinmmCapture_captureProc, self) != althrd_success) goto failure; - al_string_copy(&Device->DeviceName, VECTOR_ELEM(CaptureDevices, DeviceID)); + al_string_copy(&device->DeviceName, VECTOR_ELEM(CaptureDevices, DeviceID)); return ALC_NO_ERROR; failure: if(BufferData) { for(i = 0;i < 4;i++) - waveInUnprepareHeader(data->WaveHandle.In, &data->WaveBuffer[i], sizeof(WAVEHDR)); + waveInUnprepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR)); free(BufferData); } - if(data->Ring) - DestroyRingBuffer(data->Ring); + if(self->Ring) + DestroyRingBuffer(self->Ring); + self->Ring = NULL; - if(data->WaveHandle.In) - waveInClose(data->WaveHandle.In); + if(self->InHdl) + waveInClose(self->InHdl); + self->InHdl = NULL; - free(data); - Device->ExtraData = NULL; return ALC_INVALID_VALUE; } -static void WinMMCloseCapture(ALCdevice *Device) +static void ALCwinmmCapture_close(ALCwinmmCapture *self) { - WinMMData *data = (WinMMData*)Device->ExtraData; void *buffer = NULL; int i; /* Tell the processing thread to quit and wait for it to do so. */ - data->killNow = AL_TRUE; - PostThreadMessage(data->thread, WM_QUIT, 0, 0); + self->killNow = AL_TRUE; + PostThreadMessage(self->thread, WM_QUIT, 0, 0); - althrd_join(data->thread, &i); + althrd_join(self->thread, &i); /* Make sure capture is stopped and all pending buffers are flushed. */ - waveInReset(data->WaveHandle.In); + waveInReset(self->InHdl); // Release the wave buffers for(i = 0;i < 4;i++) { - waveInUnprepareHeader(data->WaveHandle.In, &data->WaveBuffer[i], sizeof(WAVEHDR)); - if(i == 0) buffer = data->WaveBuffer[i].lpData; - data->WaveBuffer[i].lpData = NULL; + waveInUnprepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR)); + if(i == 0) buffer = self->WaveBuffer[i].lpData; + self->WaveBuffer[i].lpData = NULL; } free(buffer); - DestroyRingBuffer(data->Ring); - data->Ring = NULL; + DestroyRingBuffer(self->Ring); + self->Ring = NULL; // Close the Wave device - waveInClose(data->WaveHandle.In); - data->WaveHandle.In = 0; - - free(data); - Device->ExtraData = NULL; + waveInClose(self->InHdl); + self->InHdl = NULL; } -static void WinMMStartCapture(ALCdevice *Device) +static ALCboolean ALCwinmmCapture_start(ALCwinmmCapture *self) { - WinMMData *data = (WinMMData*)Device->ExtraData; - waveInStart(data->WaveHandle.In); + waveInStart(self->InHdl); + return ALC_TRUE; } -static void WinMMStopCapture(ALCdevice *Device) +static void ALCwinmmCapture_stop(ALCwinmmCapture *self) { - WinMMData *data = (WinMMData*)Device->ExtraData; - waveInStop(data->WaveHandle.In); + waveInStop(self->InHdl); } -static ALCenum WinMMCaptureSamples(ALCdevice *Device, ALCvoid *Buffer, ALCuint Samples) +static ALCenum ALCwinmmCapture_captureSamples(ALCwinmmCapture *self, ALCvoid *buffer, ALCuint samples) { - WinMMData *data = (WinMMData*)Device->ExtraData; - ReadRingBuffer(data->Ring, Buffer, Samples); + ReadRingBuffer(self->Ring, buffer, samples); return ALC_NO_ERROR; } -static ALCuint WinMMAvailableSamples(ALCdevice *Device) +static ALCuint ALCwinmmCapture_availableSamples(ALCwinmmCapture *self) { - WinMMData *data = (WinMMData*)Device->ExtraData; - return RingBufferSize(data->Ring); + return RingBufferSize(self->Ring); } @@ -666,31 +721,29 @@ static inline void AppendCaptureDeviceList2(const al_string *name) AppendCaptureDeviceList(al_string_get_cstr(*name)); } -static const BackendFuncs WinMMFuncs = { - WinMMOpenPlayback, - WinMMClosePlayback, - WinMMResetPlayback, - WinMMStartPlayback, - WinMMStopPlayback, - WinMMOpenCapture, - WinMMCloseCapture, - WinMMStartCapture, - WinMMStopCapture, - WinMMCaptureSamples, - WinMMAvailableSamples, - ALCdevice_GetLatencyDefault -}; +typedef struct ALCwinmmBackendFactory { + DERIVE_FROM_TYPE(ALCbackendFactory); +} ALCwinmmBackendFactory; +#define ALCWINMMBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCwinmmBackendFactory, ALCbackendFactory) } } -ALCboolean alcWinMMInit(BackendFuncs *FuncList) +static ALCboolean ALCwinmmBackendFactory_init(ALCwinmmBackendFactory *self); +static void ALCwinmmBackendFactory_deinit(ALCwinmmBackendFactory *self); +static ALCboolean ALCwinmmBackendFactory_querySupport(ALCwinmmBackendFactory *self, ALCbackend_Type type); +static void ALCwinmmBackendFactory_probe(ALCwinmmBackendFactory *self, enum DevProbe type); +static ALCbackend* ALCwinmmBackendFactory_createBackend(ALCwinmmBackendFactory *self, ALCdevice *device, ALCbackend_Type type); + +DEFINE_ALCBACKENDFACTORY_VTABLE(ALCwinmmBackendFactory); + + +static ALCboolean ALCwinmmBackendFactory_init(ALCwinmmBackendFactory* UNUSED(self)) { VECTOR_INIT(PlaybackDevices); VECTOR_INIT(CaptureDevices); - *FuncList = WinMMFuncs; return ALC_TRUE; } -void alcWinMMDeinit() +static void ALCwinmmBackendFactory_deinit(ALCwinmmBackendFactory* UNUSED(self)) { clear_devlist(&PlaybackDevices); VECTOR_DEINIT(PlaybackDevices); @@ -699,7 +752,14 @@ void alcWinMMDeinit() VECTOR_DEINIT(CaptureDevices); } -void alcWinMMProbe(enum DevProbe type) +static ALCboolean ALCwinmmBackendFactory_querySupport(ALCwinmmBackendFactory* UNUSED(self), ALCbackend_Type type) +{ + if(type == ALCbackend_Playback || type == ALCbackend_Capture) + return ALC_TRUE; + return ALC_FALSE; +} + +static void ALCwinmmBackendFactory_probe(ALCwinmmBackendFactory* UNUSED(self), enum DevProbe type) { switch(type) { @@ -714,3 +774,39 @@ void alcWinMMProbe(enum DevProbe type) break; } } + +static ALCbackend* ALCwinmmBackendFactory_createBackend(ALCwinmmBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type) +{ + if(type == ALCbackend_Playback) + { + ALCwinmmPlayback *backend; + + backend = ALCwinmmPlayback_New(sizeof(*backend)); + if(!backend) return NULL; + memset(backend, 0, sizeof(*backend)); + + ALCwinmmPlayback_Construct(backend, device); + + return STATIC_CAST(ALCbackend, backend); + } + if(type == ALCbackend_Capture) + { + ALCwinmmCapture *backend; + + backend = ALCwinmmCapture_New(sizeof(*backend)); + if(!backend) return NULL; + memset(backend, 0, sizeof(*backend)); + + ALCwinmmCapture_Construct(backend, device); + + return STATIC_CAST(ALCbackend, backend); + } + + return NULL; +} + +ALCbackendFactory *ALCwinmmBackendFactory_getFactory(void) +{ + static ALCwinmmBackendFactory factory = ALCWINMMBACKENDFACTORY_INITIALIZER; + return STATIC_CAST(ALCbackendFactory, &factory); +}