/* * oleauto.cpp - this module: * * 1) supplies several miscellaneous helper functions, * 2) manipulates the winvile OLE automation server, and * 3) implements a visvile configuration dialog box. */ #include "stdafx.h" #include "resource.h" #include #include "w32reg.h" // located in the mainline vile development project // directory, one level "up". #include "oleauto.h" #include #define RESTORE_WDW TRUE #define NO_RESTORE_WDW FALSE // Registry configuration keys and subkeys #define CD_KEYVAL "ChdirDocPath" #define CLOSE_DOC_KEYVAL "CloseDoc" #define ENABLE_KEYVAL "Enable" #define KEY_REDIRECT "RedirectWinvileKeys" #define ROOT_CFG_KEY "Software\\Winvile\\VisVile" #define SYNC_ERRBUF_KEYVAL "SyncErrbuf" #define WRITE_BUFFERS "WriteModifiedBuffers" static size_t ansibuf_len, /* scaled in bytes */ olebuf_len; /* scaled in wchar_t */ static char *ansibuf; static OLECHAR *olebuf; static int regupdate_required; CVile *pVile = NULL; VISVILE_OPTS visvile_opts; /* ----------------------------------------------------------------- */ void visvile_regdata_dirty(void) { regupdate_required = TRUE; } /* * FUNCTION * fmt_win32_error(HRESULT errcode, char **buf, ULONG buflen) * * errcode - win32 error code for which message applies. * * buf - indirect pointer to buf to receive formatted message. If *buf * is NULL, the buffer is allocated on behalf of the client and * must be free'd using LocalFree(). * * buflen - length of buffer (specify 0 if *buf is NULL). * * DESCRIPTION * Format system error reported by Win32 API. * * RETURNS * *buf */ static char * fmt_win32_error(HRESULT errcode, char **buf, ULONG buflen) { int flags = FORMAT_MESSAGE_FROM_SYSTEM; if (! *buf) flags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; FormatMessage(flags, NULL, errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* dflt language */ (*buf) ? *buf : (LPTSTR) buf, buflen, NULL); return (*buf); } HRESULT ReportLastError(HRESULT err) { char *buf = NULL, tmp[512]; if (err != REGDB_E_CLASSNOTREG) { fmt_win32_error(err, &buf, 0); sprintf(tmp, "Unexpected error encountered.\r\rError code: %lx\rDescription: %s", err, buf); } else { strcpy(tmp, "Winvile OLE Automation registration lookup failed.\r\r" "The command 'winvile -Or' registers an OLE-aware " "version of the editor."); } ::MessageBox(NULL, tmp, PROGNAM, MB_OK|MB_ICONSTOP); if (buf) LocalFree(buf); return (err); } /* * Quick & Dirty ANSI/Unicode conversion routines. These routines use a * dynamic buffer to hold the converted string so it may be any arbitrary * size. However, the same dynamic buffer is reused when the routine is * called a second time. So make sure that the converted string is * used/copied before the conversion routine is called again. * */ #if (! defined(_UNICODE) || defined(UNICODE)) char * ConvertToAnsi(OLECHAR *szW) { size_t len; len = wcstombs(NULL, szW, 1) + 1; if (len > ansibuf_len) { if (ansibuf) free(ansibuf); ansibuf_len = ansibuf_len * 2 + len; if ((ansibuf = (char *) malloc(ansibuf_len)) == NULL) return (ansibuf); /* We're gonna' die */ } wcstombs(ansibuf, szW, len); return (ansibuf); } OLECHAR * ConvertToUnicode(char *szA) { size_t len; len = strlen(szA) + 1; if (len > olebuf_len) { if (olebuf) free(olebuf); olebuf_len = olebuf_len * 2 + len; if ((olebuf = (OLECHAR *) malloc(olebuf_len * sizeof(OLECHAR))) == NULL) return (olebuf); /* We're gonna' die */ } mbstowcs(olebuf, szA, len); return (olebuf); } #endif static DWORD get_reg_cfg_val(HKEY key, char *value_name, DWORD def_value) { DWORD val_type, result, result_size; val_type = REG_DWORD; result_size = sizeof(result); if (RegQueryValueEx(key, value_name, NULL, &val_type, (BYTE *) &result, &result_size) != ERROR_SUCCESS) { // Assume value missing -- use default. result = def_value; visvile_regdata_dirty(); } return (result); } // Read configuration values from HKEY_CURRENT_USER\Software\Winvile\VisVile static void get_addin_config(void) { HKEY hk; // Supply fallback, default VisVile configuration values. visvile_opts.enabled = visvile_opts.close_ds_doc = visvile_opts.write_buffers = TRUE; // All other options default to FALSE.... // Now, read "old" config values from registry (if they exist). if (RegOpenKeyEx(HKEY_CURRENT_USER, ROOT_CFG_KEY, 0, KEY_READ, &hk) == ERROR_SUCCESS) { visvile_opts.cd_doc_dir = get_reg_cfg_val(hk, CD_KEYVAL, visvile_opts.cd_doc_dir); visvile_opts.close_ds_doc = get_reg_cfg_val(hk, CLOSE_DOC_KEYVAL, visvile_opts.close_ds_doc); visvile_opts.enabled = get_reg_cfg_val(hk, ENABLE_KEYVAL, visvile_opts.enabled); visvile_opts.sync_errbuf = get_reg_cfg_val(hk, SYNC_ERRBUF_KEYVAL, visvile_opts.sync_errbuf); visvile_opts.write_buffers = get_reg_cfg_val(hk, WRITE_BUFFERS, visvile_opts.write_buffers); visvile_opts.key_redir = pVile->DsHwnd() ? get_reg_cfg_val(hk, KEY_REDIRECT, visvile_opts.key_redir) : FALSE; RegCloseKey(hk); } else { /* * No configuration info stored in registry. Force registry * update when visvile exits. */ visvile_regdata_dirty(); } } static void set_reg_cfg_val(HKEY key, char *value_name, DWORD value) { long rc; if ((rc = RegSetValueEx(key, value_name, NULL, REG_DWORD, (BYTE *) &value, sizeof(value))) != ERROR_SUCCESS) { ReportLastError(rc); } } // Write configuration values to HKEY_CURRENT_USER\Software\Winvile\VisVile static void set_addin_config(void) { HKEY hk; LONG rc; if ((rc = RegCreateKeyEx(HKEY_CURRENT_USER, ROOT_CFG_KEY, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hk, NULL)) != ERROR_SUCCESS) { ReportLastError(rc); return; } set_reg_cfg_val(hk, CD_KEYVAL, visvile_opts.cd_doc_dir); set_reg_cfg_val(hk, CLOSE_DOC_KEYVAL, visvile_opts.close_ds_doc); set_reg_cfg_val(hk, ENABLE_KEYVAL, visvile_opts.enabled); set_reg_cfg_val(hk, SYNC_ERRBUF_KEYVAL, visvile_opts.sync_errbuf); set_reg_cfg_val(hk, WRITE_BUFFERS, visvile_opts.write_buffers); set_reg_cfg_val(hk, KEY_REDIRECT, visvile_opts.key_redir); RegCloseKey(hk); } HRESULT oleauto_init(void) { olebuf_len = ansibuf_len = 512; ansibuf = (char *) malloc(ansibuf_len); olebuf = (OLECHAR *) malloc(olebuf_len * sizeof(OLECHAR)); /* * CVile's constructor argument extracts a handle to the DevStudio 5 * frame using a sequence of win32 calls that was determined by trial * and error. 'Twill be interesting to see if this works with later * DevStudio versions. [works with DevStudio 6.0] */ pVile = new CVile(::GetParent(::GetActiveWindow())); get_addin_config(); // Execute this code _after_ creating pVile. if (! (olebuf && ansibuf && pVile)) return (ReportLastError(E_OUTOFMEMORY)); else return (S_OK); } void oleauto_exit(void) { if (regupdate_required) set_addin_config(); if (pVile) { (void) pVile->key_redir_change(FALSE); // key redirection killed delete pVile; } if (olebuf) free(olebuf); if (ansibuf) free(ansibuf); } /* ------------------------------ CVile ------------------------- */ void CVile::Disconnect() { if (pVileAuto) { (void) pVileAuto->Release(); pVileAuto = NULL; } } bool CVile::Connected() { bool connected = FALSE; VARIANT_BOOL visible; if (pVileAuto) { /* * We have a connection to a winvile instance (maybe). Obtain * winvile application visibility state to verify connection. */ connected = SUCCEEDED(pVileAuto->get_Visible(&visible)); } return (connected); } // Enable or disable key redirection from winvile to DevStudio. HRESULT CVile::key_redir_change(int enable) { HRESULT hr = S_OK; if (Connected()) { hr = pVileAuto->WindowRedirect((enable) ? (DWORD) ds_hwnd : NULL); if (FAILED(hr)) Disconnect(); // Certainly didn't expect that. } return (hr); } HRESULT CVile::Connect(int restore_wdw, VARIANT_BOOL *in_insert_mode) { HRESULT hr; *in_insert_mode = VARIANT_TRUE; // Set a known, safe value. if (pVileAuto) { VARIANT_BOOL visible; /* * We have a connection to a winvile instance (maybe). First obtain * winvile application visibility state, which provides a true test * of whether or not the connection exists. */ hr = pVileAuto->get_Visible(&visible); if (FAILED(hr)) { // Assume the user has closed our previous editor instance. Disconnect(); // kills editor connection and nulls pVileAuto. } else if (restore_wdw && ! visible) { hr = pVileAuto->put_Visible(TRUE); if (FAILED(hr)) Disconnect(); // Certainly didn't expect that. } } if (! pVileAuto) { LPUNKNOWN punk; hr = CoCreateInstance(CLSID_VileAuto, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void **) &punk); if (FAILED(hr)) return (hr); hr = punk->QueryInterface(IID_IVileAuto, (void **) &pVileAuto); punk->Release(); if (FAILED(hr)) return (hr); if (restore_wdw) { hr = pVileAuto->put_Visible(TRUE); // Make the editor visible. if (FAILED(hr)) Disconnect(); // Certainly didn't expect that. } if (pVileAuto && visvile_opts.key_redir) { // Send Winvile the HWND that receives redirected keystrokes. hr = pVileAuto->WindowRedirect((DWORD) ds_hwnd); if (FAILED(hr)) Disconnect(); // Certainly didn't expect that. } } if (SUCCEEDED(hr)) { if (restore_wdw) { VARIANT_BOOL minimized; /* Restore editor if minimized. */ hr = pVileAuto->get_IsMinimized(&minimized); if (SUCCEEDED(hr) && minimized) hr = pVileAuto->Restore(); } if (SUCCEEDED(hr)) { // return editor's "mode" state to client hr = pVileAuto->get_InsertMode(in_insert_mode); } } return (hr); } HRESULT CVile::FileOpen(BSTR filename, long lineno) { BSTR bcmd; char cmd[1024], line[64], cd[512]; HRESULT hr; VARIANT_BOOL in_insert_mode; // editor in insert mode? OLECHAR *ole; hr = Connect(RESTORE_WDW, &in_insert_mode); if (SUCCEEDED(hr)) { sprintf(cmd, "%s:e %S\n", (in_insert_mode) ? "\033" : "", // prepend ESC if necessary filename); if (visvile_opts.cd_doc_dir) { char *tmp, *cp; if ((tmp = FROM_OLE_STRING(filename)) == NULL) return (E_OUTOFMEMORY); if ((cp = strrchr(tmp, '\\')) != NULL) { *cp = '\0'; sprintf(cd, ":cd %s\n", tmp); *cp = '\\'; strcat(cmd, cd); } } if (lineno > 1) { sprintf(line, ":%ld\n", lineno); strcat(cmd, line); } ole = TO_OLE_STRING(cmd); bcmd = SysAllocString(ole); if (! (bcmd && ole)) return (E_OUTOFMEMORY); hr = pVileAuto->VileKeys(bcmd); SysFreeString(bcmd); // don't need to free "ole", it points at a // global, dynamic tmp buffer. if (SUCCEEDED(hr)) FgWindow(); } return (hr); } void CVile::FgWindow() { HWND hwnd; // Set foreground window using a method that's compatible with win2k pVileAuto->get_MainHwnd((LONG *) &hwnd); (void) ::SetForegroundWindow(hwnd); } HRESULT CVile::VileCmd( const char *cmd, bool restore_wdw, // T -> make editor visible and pop its window // on top. bool force_connection // T -> force connection to editor if // necessary. // F -> don't execute command if editor // disconnected. ) { BSTR bcmd; HRESULT hr; VARIANT_BOOL in_insert_mode; // editor in insert mode? OLECHAR *ole; char *scratch = NULL; if (! force_connection && ! Connected()) { // The command should not be executed. return (S_OK); } hr = Connect(restore_wdw ? RESTORE_WDW : NO_RESTORE_WDW, &in_insert_mode); if (SUCCEEDED(hr)) { if (in_insert_mode) { // Prepend ESC to command. if ((scratch = (char *) malloc(strlen(cmd) + 2)) == NULL) return (E_OUTOFMEMORY); scratch[0] = '\033'; // ESC strcpy(scratch + 1, cmd); } ole = TO_OLE_STRING((scratch) ? (char *) scratch : (char *) cmd); bcmd = SysAllocString(ole); if (! (bcmd && ole)) return (E_OUTOFMEMORY); if (restore_wdw) { /* * Pop editor to the front. Note that there's a subtle * sequencing problem here. Some command strings will require * restoration of the editor's window _before_ the string is * executed. Why? The string may be attempting to open a file * that's created as a side effect of a DevStudio operation * that _follows_ execution of "cmd". In that situation, * visvile can't be waiting for the editor to finish execting * "cmd" before directing the editor to pop to the front. Why? * 'Cause until visvile returns control to DevStudio, DevStudio * won't create the target file. Nasty, eh? Sure wasted an * evening of my life. */ FgWindow(); // Pop editor to front. } hr = pVileAuto->VileKeys(bcmd); SysFreeString(bcmd); // don't need to free "ole", it points at a // global, dynamic tmp buffer. if (scratch) free(scratch); } return (hr); } ///////////////////////////////////////////////////////////////////////////// // CConfigDlg dialog CConfigDlg::CConfigDlg(CWnd* pParent /*=NULL*/) : CDialog(CConfigDlg::IDD, pParent) { //{{AFX_DATA_INIT(CConfigDlg) m_enabled = FALSE; m_sync_errbuf = FALSE; m_close_ds_doc = FALSE; m_cd_doc_dir = FALSE; m_write_buffers = FALSE; m_key_redir = FALSE; //}}AFX_DATA_INIT } void CConfigDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CConfigDlg) DDX_Check(pDX, IDC_ENABLED, m_enabled); DDX_Check(pDX, IDC_ERRBUF, m_sync_errbuf); DDX_Check(pDX, IDC_CLOSE_DOC, m_close_ds_doc); DDX_Check(pDX, IDC_CHDIR, m_cd_doc_dir); DDX_Check(pDX, IDC_WRITE_BUFFERS, m_write_buffers); DDX_Check(pDX, IDC_KEY_REDIR, m_key_redir); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CConfigDlg, CDialog) //{{AFX_MSG_MAP(CConfigDlg) ON_BN_CLICKED(IDC_ENABLED, OnEnabled) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CConfigDlg message handlers BOOL CConfigDlg::OnInitDialog() { m_cd_doc_dir = visvile_opts.cd_doc_dir; m_close_ds_doc = visvile_opts.close_ds_doc; m_enabled = visvile_opts.enabled; m_sync_errbuf = visvile_opts.sync_errbuf; m_write_buffers = visvile_opts.write_buffers; m_key_redir = visvile_opts.key_redir; /* * Now, depending on value of m_enabled, manually enable/disable all * other controls. I'm doing this because I'm learning MFC/Windows * programming, not because I particularly care if the dialog box * responds to user input or not. */ update_dlg_control_states(FALSE); return (CDialog::OnInitDialog()); } void CConfigDlg::OnEnabled() { // Called when user clicks the Enabled check box. update_dlg_control_states(TRUE); } void CConfigDlg::update_dlg_control_states(bool dialog_up) { if (dialog_up) UpdateData(TRUE); // copy current dlg data into member vars. update_dlg_control(IDC_CLOSE_DOC, dialog_up); update_dlg_control(IDC_CHDIR, dialog_up); update_dlg_control(IDC_WRITE_BUFFERS, dialog_up); update_dlg_control(IDC_ERRBUF, dialog_up); update_dlg_control(IDC_KEY_REDIR, dialog_up); } void CConfigDlg::update_dlg_control(int ctrl_id, bool dialog_up) { CWnd *hctrl; if ((hctrl = GetDlgItem(ctrl_id)) != NULL) { if (ctrl_id != IDC_KEY_REDIR) hctrl->EnableWindow(m_enabled); else { /* * The enable key redirect feature is _always_ enabled so long * as the DevStudio window handle is not NULL. Keeping the * redirect feature enabled at all times permits the winvile * user to redirect keys to DevStudio even if the add-in is * disabled (this is a good thing). */ hctrl->EnableWindow(pVile->DsHwnd() != NULL); } if (dialog_up) hctrl->UpdateWindow(); } }