diff --git a/ui/app/toolkits/qt/dist/windows/.gitignore b/ui/app/toolkits/qt/dist/windows/.gitignore new file mode 100644 index 000000000..fe1253145 --- /dev/null +++ b/ui/app/toolkits/qt/dist/windows/.gitignore @@ -0,0 +1 @@ +.32 diff --git a/ui/app/toolkits/qt/dist/windows/CMakeLists.txt b/ui/app/toolkits/qt/dist/windows/CMakeLists.txt index 6e0b437c2..513582c32 100644 --- a/ui/app/toolkits/qt/dist/windows/CMakeLists.txt +++ b/ui/app/toolkits/qt/dist/windows/CMakeLists.txt @@ -70,3 +70,34 @@ else() endif() add_custom_target(installer DEPENDS ${INSTALLER_TARGET}) + +if (CMAKE_CROSSCOMPILING) + ExternalProject_Add(ZapperQt32Bit + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src + BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/.32 + CMAKE_CACHE_ARGS + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_SOURCE_DIR}/cmake/toolchains/mingw32-gcc.cmake + -DCMAKE_INSTALL_PREFIX:FILEPATH=${CMAKE_INSTALL_PREFIX} + ) +elseif (MSVC) + ExternalProject_Add(ZapperQt32Bit + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src + BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/.32 + CMAKE_GENERATOR "Visual Studio 17 2022" + CMAKE_ARGS -A Win32 + CMAKE_CACHE_ARGS + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_INSTALL_PREFIX:FILEPATH=${CMAKE_INSTALL_PREFIX} + -DVCPKG_TARGET_TRIPLET:STRING=x86-windows + ) +elseif (MINGW) + set(CMAKE_COMMAND32 ${MSYS_CMD} -here -clang32 -no-start -defterm -c "cmake \$*" cmake) + ExternalProject_Add(ZapperQt32Bit + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src + BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/.32 + CMAKE_COMMAND ${CMAKE_COMMAND32} + CMAKE_CACHE_ARGS + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${CMAKE_SOURCE_DIR}/cmake/toolchains/msys2.cmake + -DCMAKE_INSTALL_PREFIX:FILEPATH=${CMAKE_INSTALL_PREFIX} + ) +endif() diff --git a/ui/app/toolkits/qt/dist/windows/setup.iss.in b/ui/app/toolkits/qt/dist/windows/setup.iss.in index 376c523f6..ae14ccc1d 100644 --- a/ui/app/toolkits/qt/dist/windows/setup.iss.in +++ b/ui/app/toolkits/qt/dist/windows/setup.iss.in @@ -35,34 +35,23 @@ WizardImageFile=@INSTALL_WIN_PATH@\dist\WizModernImage.bmp WizardSmallImageFile=@INSTALL_WIN_PATH@\dist\WizModernSmall.bmp PrivilegesRequired=none CloseApplications=no -ArchitecturesInstallIn64BitMode=x64 +ArchitecturesInstallIn64BitMode=x64compatible #ifdef SignTool SignTool={#SignTool} SignedUninstaller=yes #endif -; uncomment the following line if you want your installation to run on NT 3.51 too. -; MinVersion=4,3.51 - -[Types] -Name: "full"; Description: "Full installation" -;;Name: "compact"; Description: "Compact installation (excludes DBus runtime)" - -[Components] -Name: "main"; Description: "Main Files"; Types: full; Flags: fixed -Name: "dbus"; Description: "D-BUS Runtime Environment"; Types: full -Name: "applet"; Description: "Windows Taskbar applet"; Types: full - [Tasks] -Name: "desktopicon"; Description: "Create a &desktop icon"; GroupDescription: "Additional tasks:"; MinVersion: 4,4 -Name: "startupmenu"; Description: "Start Workrave when Windows starts"; GroupDescription: "Additional tasks:"; MinVersion: 4,4 +Name: "desktopicon"; Description: "Create a &desktop icon"; GroupDescription: "Additional tasks:" +Name: "startupmenu"; Description: "Start Workrave when Windows starts"; GroupDescription: "Additional tasks:" +Name: "autoupdate"; Description: "Automatically check for updates"; GroupDescription: "Additional tasks:" [Files] Source: "@INSTALL_WIN_PATH@\dist\libzapper-0.dll"; DestDir: "{app}"; Source: "@INSTALL_WIN_PATH@\bin\harpoon64.dll"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs restartreplace uninsrestartdelete; -Source: "@INSTALL_WIN_PATH@\bin\workrave-applet64.dll"; DestDir: "{app}\bin"; Flags: ignoreversion restartreplace uninsrestartdelete regserver; Components: applet; Check: IsAdmin; +Source: "@INSTALL_WIN_PATH@\bin\workrave-applet64.dll"; DestDir: "{app}\bin"; Flags: ignoreversion restartreplace uninsrestartdelete regserver; Check: IsAdmin and (not IsWindows11OrLater()); Source: "@INSTALL_WIN_PATH@\bin\workrave.exe"; DestDir: "{app}\bin"; DestName: "Workrave.exe"; Flags: ignoreversion; -Source: "@INSTALL_WIN_PATH@\bin\gdbus.exe"; DestDir: "{app}\bin"; Flags: ignoreversion; Components: dbus; +Source: "@INSTALL_WIN_PATH@\bin\gdbus.exe"; DestDir: "{app}\bin"; Flags: ignoreversion; Source: "@INSTALL_WIN_PATH@\lib\plugins\*.*"; DestDir: "{app}\lib\plugins"; Flags: ignoreversion recursesubdirs restartreplace uninsrestartdelete; Source: "@INSTALL_WIN_PATH@\share\*.*"; DestDir: "{app}\share\"; Flags: ignoreversion recursesubdirs; Source: "@INSTALL_WIN_PATH@\COPYING.txt"; DestDir: "{app}"; DestName: "COPYING.txt"; Flags: ignoreversion; @@ -87,13 +76,15 @@ Name: "{autodesktop}\Workrave"; Filename: "{app}\bin\Workrave.exe"; MinVersion: Name: "{app}\Workrave"; Filename: "{app}\bin\Workrave.exe" [Run] -Filename: "{app}\bin\Workrave.exe"; Description: "Launch Workrave"; Flags: nowait postinstall skipifsilent shellexec; +Filename: "{app}\bin\Workrave.exe"; Description: "Launch Workrave"; Flags: nowait postinstall shellexec; [InstallDelete] Type: files; Name: "{autostartup}\Workrave.lnk" Type: files; Name: "{app}\share\sounds\*.wav" [Code] +Var + taskPageShown: boolean; function FindWorkrave(): Boolean; external 'FindWorkrave@{tmp}\libzapper-0.dll stdcall delayload'; @@ -104,6 +95,14 @@ external 'ZapWorkrave@{tmp}\libzapper-0.dll stdcall delayload'; function KillProcess(name : String): Boolean; external 'KillProcess@{tmp}\libzapper-0.dll cdecl delayload'; +function AreWorkraveProcessesRunning(name : String): Boolean; +external 'AreWorkraveProcessesRunning@{tmp}\libzapper-0.dll cdecl delayload'; + +function KillWorkraveProcesses(name : String): Boolean; +external 'KillWorkraveProcesses@{tmp}\libzapper-0.dll cdecl delayload'; + +function GetInstallLocation(): String; forward; + Function FindWorkraveWithRetries() : Boolean; var retVal : Boolean; var count : Integer; @@ -122,56 +121,130 @@ begin Result := retVal; end; -Function EnsureWorkraveIsNotRunning() : Boolean; -var retVal : Boolean; +Function IsWorkraveRunning() : Boolean; +var isRunning : Boolean; +var installLocation: String; +begin + installLocation := GetInstallLocation(); + isRunning := FindWorkraveWithRetries; + if (not isRunning) and (installLocation <> '') then + begin + isRunning := AreWorkraveProcessesRunning(installLocation); + end; + Result := isRunning; +end; + +Function IsWorkraveTerminated() : Boolean; +var isRunning : Boolean; +var installLocation: String; +var count : Integer; +begin + installLocation := GetInstallLocation(); + count := 50; + isRunning := True + + while ((count > 0) and (isRunning)) do + begin + isRunning := FindWorkrave; + if (not isRunning) and (installLocation <> '') then + begin + isRunning := AreWorkraveProcessesRunning(installLocation); + end; + if isRunning then + begin + Sleep(100) + end; + count := count - 1; + end; + + Result := isRunning; +end; + +Function EnsureWorkraveIsNotRunning(silent : Boolean) : Boolean; +var installLocation: String; begin Result := True; - try - retVal := FindWorkraveWithRetries; - if retVal then + if IsWorkraveRunning() then begin - if MsgBox('Workrave is still running. Setup must close Workrave before continuing. Please click OK to continue, or Cancel to exit', - mbConfirmation, MB_OKCANCEL) = IDOK then + if not silent then + begin + Result := MsgBox('Workrave is still running. Setup must close Workrave before continuing. Please click OK to continue, or Cancel to exit', + mbConfirmation, MB_OKCANCEL) = IDOK + end; + + if Result then begin - retVal := ZapWorkrave(); - if retVal then + installLocation := GetInstallLocation(); + + ZapWorkrave(); + if installLocation <> '' then begin - retVal := FindWorkraveWithRetries(); - if retVal then + if (not IsWorkraveTerminated()) then begin - KillProcess('workrave.exe'); - retVal := FindWorkrave(); + KillWorkraveProcesses(installLocation); end; - if retVal then - begin - MsgBox('Failed to close Workrave. Please close Workrave manually.', mbError, MB_OK); - Result := False; - end - end - end - else + end; + + if IsWorkraveTerminated() then + begin + MsgBox('Failed to close Workrave. Please close Workrave manually.', mbError, MB_OK); + Result := False; + end; + end; + end; +end; + +function ShouldUninstall(): Boolean; +var + versionMS, versionLS: Cardinal; + major, minor: Integer; + directories: array[0..1] of String; + i: Integer; + executablePath: String; + installLocation: String; +begin + directories[0] := 'lib'; + directories[1] := 'bin'; + installLocation := GetInstallLocation(); + + if installLocation <> '' then + begin + for i := 0 to High(directories) do + begin + executablePath := ExpandConstant(installLocation + directories[i] + '\workrave.exe'); + if FileExists(executablePath) and GetVersionNumbers(executablePath, versionMS, versionLS) then begin - Result := False; - end + major := versionMS shr 16; + minor := versionMS and $FFFF; + + if (major < 1) or ((major = 1) and (minor < 11)) then + Result := True + else + Result := False; + Exit; + end; end; - KillProcess('dbus-daemon.exe'); - KillProcess('harpoonHelper.exe'); - KillProcess('WorkraveHelper.exe'); - except - MsgBox('Failed to close Workrave. Please close Workrave manually.', mbError, MB_OK); end; + + Result := False; +end; + +function IsWindows11OrLater(): Boolean; +begin + Result := (GetWindowsVersion >= $0A0055F0); end; Function InitializeSetup() : Boolean; begin + taskPageShown := False; ExtractTemporaryFile('libzapper-0.dll'); - Result := EnsureWorkraveIsNotRunning(); + Result := EnsureWorkraveIsNotRunning(WizardSilent()); end; Function InitializeUninstall() : Boolean; begin FileCopy(ExpandConstant('{app}\libzapper-0.dll'), ExpandConstant('{tmp}\libzapper-0.dll'), False); - Result := EnsureWorkraveIsNotRunning(); + Result := EnsureWorkraveIsNotRunning(UninstallSilent()); end; function GetAppPath(S: String): String; @@ -179,8 +252,110 @@ begin Result := WizardDirValue + '\bin'; end; -[Code] function IsX64: Boolean; begin Result := (ProcessorArchitecture = paX64); end; + +Procedure UpdateAutoInstallTask(); +var + success: boolean; + enabledStr: string; + enabled: boolean; +begin + enabled := False; + + success := RegQueryStringValue(HKCU, 'Software\Workrave\plugins\auto_update', 'enabled', enabledStr); + if success and (enabledStr = '1') then + begin + enabled := True; + end; + + if enabled then + begin + WizardSelectTasks('autoupdate'); + end + else + begin + WizardSelectTasks('!autoupdate'); + end +end; + +Procedure UpdateAutoInstallSetting(); +var + enabledStr: string; +begin + if WizardIsTaskSelected('autoupdate') then + enabledStr := '1' + else + enabledStr := '0'; + RegWriteStringValue(HKCU, 'Software\Workrave\plugins\auto_update', 'enabled', enabledStr); +#ifdef Channel + RegWriteStringValue(HKCU, 'Software\Workrave\plugins\auto_update', 'channel', '{#Channel}'); +#endif +end; + +function GetUninstallCommand(): String; +var + uninstallPath: String; + uninstallCommand: String; +begin + uninstallPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\Workrave_is1'); + uninstallCommand := ''; + + if not RegQueryStringValue(HKLM, uninstallPath, 'UninstallString', uninstallCommand) then + RegQueryStringValue(HKCU, uninstallPath, 'UninstallString', uninstallCommand); + Result := uninstallCommand; +end; + +function GetInstallLocation(): String; +var + uninstallPath: String; + installLocation: String; +begin + uninstallPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\Workrave_is1'); + installLocation := ''; + + if not RegQueryStringValue(HKLM, uninstallPath, 'InstallLocation', installLocation) then + RegQueryStringValue(HKCU, uninstallPath, 'InstallLocation', installLocation); + Result := installLocation; +end; + +Procedure UninstallPreviousVersion(); +var + uninstallCommand: String; + iResultCode: Integer; +begin + uninstallCommand := GetUninstallCommand(); + + if uninstallCommand <> '' then begin + uninstallCommand := RemoveQuotes(uninstallCommand); + Exec(uninstallCommand, '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode); + end +end; + +Procedure CurPageChanged(CurPageID: Integer); +begin + if (CurPageID = wpSelectTasks) and not taskPageShown then + begin + taskPageShown := True; + UpdateAutoInstallTask(); + end +end; + +procedure CurStepChanged(CurStep: TSetupStep); +begin + case CurStep of + ssInstall: + begin + if ShouldUninstall() then + begin + UninstallPreviousVersion(); + end; + end; + ssDone: + begin + UpdateAutoInstallSetting(); + end + end; +end; diff --git a/ui/app/toolkits/qt/dist/windows/src/CMakeLists.txt b/ui/app/toolkits/qt/dist/windows/src/CMakeLists.txt new file mode 100644 index 000000000..16f1dee16 --- /dev/null +++ b/ui/app/toolkits/qt/dist/windows/src/CMakeLists.txt @@ -0,0 +1,17 @@ + +cmake_minimum_required(VERSION 3.30) +project(zapper) +cmake_policy(VERSION 3.30) + +set(SRC zapper.c) + +add_library(zapper-0 SHARED ${SRC}) +if (MINGW) + set_target_properties(zapper-0 PROPERTIES LINK_FLAGS "-static-libgcc") +endif() +if (MSVC) + set_target_properties(zapper-0 PROPERTIES COMPILE_FLAGS "-DWIN32_LEAN_AND_MEAN") +endif() +set_target_properties(zapper-0 PROPERTIES PREFIX "lib") + +install (TARGETS zapper-0 RUNTIME DESTINATION dist) diff --git a/ui/app/toolkits/qt/dist/windows/src/zapper.c b/ui/app/toolkits/qt/dist/windows/src/zapper.c new file mode 100644 index 000000000..618269b13 --- /dev/null +++ b/ui/app/toolkits/qt/dist/windows/src/zapper.c @@ -0,0 +1,311 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "zapper.h" + +#if defined(_MSC_VER) +# pragma comment(lib, "user32.lib") + +# pragma warning(push) +# pragma warning(disable : 4100) // unreferenced formal parameter +#endif + +typedef DWORD(__stdcall *QUERYFULLPROCESSIMAGENAME)(HANDLE, DWORD, LPTSTR, PDWORD); +typedef DWORD(__stdcall *GETMODULEFILENAMEEX)(HANDLE, HMODULE, LPTSTR, DWORD); + +static QUERYFULLPROCESSIMAGENAME pfnQueryFullProcessImageName = NULL; +static GETMODULEFILENAMEEX pfnGetModuleFileNameEx = NULL; + +static BOOL success = FALSE; +static BOOL simulate = FALSE; + +#define MAX_CLASS_NAME (128) +#define MAX_TITLE (128) + +enum Kind +{ + KIND_NONE, + KIND_EGG, + KIND_MENU +}; + +static BOOL +GetProcessName(HWND hwnd, char *buf, size_t buf_size) +{ + HANDLE process_handle = NULL; + BOOL process_name_found = FALSE; + DWORD processid = 0; + + if (GetWindowThreadProcessId(hwnd, &processid) && processid != 0) + { + process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, processid); + if (process_handle != 0) + { + if (pfnQueryFullProcessImageName != NULL) + { + DWORD size = buf_size; + if (pfnQueryFullProcessImageName(process_handle, 0, buf, &size)) + { + process_name_found = TRUE; + } + } + + if (!process_name_found && pfnGetModuleFileNameEx) + { + if (pfnGetModuleFileNameEx(process_handle, NULL, buf, buf_size)) + { + process_name_found = TRUE; + } + } + CloseHandle(process_handle); + } + } + return process_name_found; +} + +static void +SendQuit(HWND hwnd, enum Kind kind) +{ + switch (kind) + { + case KIND_NONE: + break; + + case KIND_EGG: + PostMessage(hwnd, WM_ENDSESSION, 1, 0); + break; + + case KIND_MENU: + PostMessage(hwnd, WM_USER, 14, 0); + break; + } +} + +static BOOL CALLBACK +EnumWindowsProc(HWND hwnd, LPARAM lParam) +{ + char className[MAX_CLASS_NAME] = { + 0, + }; + char title[MAX_TITLE] = { + 0, + }; + char processName[2 * MAX_PATH] = { + 0, + }; + int n = 0; + enum Kind kind = KIND_NONE; + + n = GetClassName(hwnd, (LPSTR)className, sizeof(className) - 1); + if (n < 0) + { + n = 0; + } + className[n] = '\0'; + // printf("className = %s\n", className); + + n = GetWindowText(hwnd, (LPSTR)title, sizeof(title) - 1); + if (n < 0) + { + n = 0; + } + title[n] = '\0'; + // printf("title = %s\n", title); + + if (strcmp(className, "EggSmClientWindow") == 0) + { + kind = KIND_EGG; + } + else if ((strcmp(className, "gdkWindowToplevel") == 0) && (strcmp(title, "Workrave") == 0)) + { + kind = KIND_MENU; + } + // printf("kind = %d\n", kind); + + if (kind != KIND_NONE) + { + BOOL ret = GetProcessName(hwnd, processName, sizeof(processName)); + + if (ret) + { + // printf("processName = %s\n", processName); + char *ptr = strrchr(processName, '\\'); + if (ptr != NULL && _stricmp(ptr + 1, "Workrave.exe") == 0) + { + success = TRUE; + if (!simulate) + { + SendQuit(hwnd, kind); + } + } + } + } + return !success; +} + +static void +FindOrZapWorkrave(void) +{ + HINSTANCE psapi = NULL; + HINSTANCE kernel32 = LoadLibrary("kernel32.dll"); + + if (kernel32 != NULL) + { + pfnQueryFullProcessImageName = (QUERYFULLPROCESSIMAGENAME)GetProcAddress(kernel32, "QueryFullProcessImageNameA"); + } + + if (pfnQueryFullProcessImageName == NULL) + { + psapi = LoadLibrary("psapi.dll"); + pfnGetModuleFileNameEx = (GETMODULEFILENAMEEX)GetProcAddress(psapi, "GetModuleFileNameExA"); + } + + if (pfnQueryFullProcessImageName != NULL || pfnGetModuleFileNameEx != NULL) + { + EnumWindows(EnumWindowsProc, 0L); + } + + if (kernel32 != NULL) + { + FreeLibrary(kernel32); + } + + if (psapi != NULL) + { + FreeLibrary(psapi); + } +} + +BOOL +FindWorkrave(void) +{ + success = FALSE; + simulate = TRUE; + + FindOrZapWorkrave(); + + return success; +} + +BOOL +ZapWorkrave(void) +{ + success = FALSE; + simulate = FALSE; + + FindOrZapWorkrave(); + + return success; +} + +static bool +IsStringInList(const char *str, const char **list) +{ + while (*list != NULL) + { + if (_stricmp(str, *list) == 0) + { + return true; + } + list++; + } + return false; +} + +static bool +MatchProcess(DWORD process_id, const char *directory, const char **allowed_executable_names) +{ + HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_id); + + if (process_handle != NULL) + { + char process_path[MAX_PATH] = {0}; + if (GetModuleFileNameEx(process_handle, NULL, process_path, MAX_PATH) > 0) + { + if (strstr(process_path, directory) == process_path) + { + char *executable_name = strrchr(process_path, '\\'); + if (executable_name != NULL) + { + executable_name++; + if (IsStringInList(executable_name, allowed_executable_names)) + { + CloseHandle(process_handle); + return true; + } + } + } + } + CloseHandle(process_handle); + } + return false; +} + + + +int +TerminateProcessesByNames(const char *directory, const char **executable_names_to_kill, bool dry_run) +{ + HANDLE process_snapshot_handle = NULL; + PROCESSENTRY32 process_entry; + + process_snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (process_snapshot_handle == INVALID_HANDLE_VALUE) + { + return -1; + } + + process_entry.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(process_snapshot_handle, &process_entry)) + { + CloseHandle(process_snapshot_handle); + return -1; + } + + int ret = 0; + do + { + if (MatchProcess(process_entry.th32ProcessID, directory, executable_names_to_kill)) + { + ret = 1; + if (!dry_run) + { + HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, process_entry.th32ProcessID); + if (process_handle != NULL) + { + TerminateProcess(process_handle, 1); + CloseHandle(process_handle); + } + } + } + } + while (Process32Next(process_snapshot_handle, &process_entry)); + + CloseHandle(process_snapshot_handle); + return ret; +} + +static const char *workrave_executables[] = + {"Workrave.exe", "workrave.exe", "WorkraveHelper.exe", "gdbus.exe", "harpoonHelper.exe", "dbus-daemon.exe", NULL}; + +BOOL +AreWorkraveProcessesRunning(const char *directory) +{ + return TerminateProcessesByNames(directory, workrave_executables, true) == 1; +} + +BOOL +KillWorkraveProcesses(const char *directory) +{ + return TerminateProcessesByNames(directory, workrave_executables, false) != -1; +} + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif diff --git a/ui/app/toolkits/qt/dist/windows/src/zapper.h b/ui/app/toolkits/qt/dist/windows/src/zapper.h new file mode 100644 index 000000000..2246f8765 --- /dev/null +++ b/ui/app/toolkits/qt/dist/windows/src/zapper.h @@ -0,0 +1,11 @@ +#ifndef ZAPPER_H +#define ZAPPER_H + +__declspec(dllexport) BOOL ZapWorkrave(void); +__declspec(dllexport) BOOL FindWorkrave(void); + +__declspec(dllexport) int TerminateProcessesByNames(const char *directory, const char **executable_names_to_kill, bool dry_run); +__declspec(dllexport) BOOL AreWorkraveProcessesRunning(const char *directory); +__declspec(dllexport) BOOL KillWorkraveProcesses(const char *directory); + +#endif /* ZAPPER_H */