]> git.lyx.org Git - lyx.git/blob - src/support/os_win32.cpp
b450bf1100553040659989ca325a4cf5b1873292
[lyx.git] / src / support / os_win32.cpp
1 /**
2  * \file os_win32.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Ruurd A. Reitsma
7  * \author Claus Hentschel
8  * \author Angus Leeming
9  *
10  * Full author contact details are available in file CREDITS.
11  *
12  * Various OS specific functions
13  */
14
15 #include <config.h>
16
17 #include "support/os.h"
18 #include "support/os_win32.h"
19
20 #include "support/debug.h"
21 #include "support/FileName.h"
22 #include "support/gettext.h"
23 #include "support/filetools.h"
24 #include "support/lstrings.h"
25 #include "support/ExceptionMessage.h"
26
27 #include "support/lassert.h"
28
29 #include <csignal>
30 #include <cstdlib>
31 #include <vector>
32
33 /* The GetLongPathName macro may be defined on the compiling machine,
34  * but we must use a bit of trickery if the resulting executable is
35  * to run on a Win95 machine.
36  * Fortunately, Microsoft provide the trickery. All we need is the
37  * NewAPIs.h header file, available for download from Microsoft as
38  * part of the Platform SDK.
39  */
40 #if defined (HAVE_NEWAPIS_H)
41 // This should be defined already to keep Boost.Filesystem happy.
42 # if !defined (WANT_GETFILEATTRIBUTESEX_WRAPPER)
43 #   error Expected WANT_GETFILEATTRIBUTESEX_WRAPPER to be defined!
44 # endif
45 # define WANT_GETLONGPATHNAME_WRAPPER 1
46 # define COMPILE_NEWAPIS_STUBS
47 # include <NewAPIs.h>
48 # undef COMPILE_NEWAPIS_STUBS
49 # undef WANT_GETLONGPATHNAME_WRAPPER
50 #endif
51
52 #include <io.h>
53 #include <direct.h> // _getdrive
54 #include <shlobj.h>  // SHGetFolderPath
55 #include <windef.h>
56 #include <shellapi.h>
57 #include <shlwapi.h>
58
59 // Must define SHGFP_TYPE_CURRENT for older versions of MinGW.
60 #if defined(__MINGW32__)  || defined(__CYGWIN__) || defined(__CYGWIN32__)
61 # include <w32api.h>
62 # if __W32API_MAJOR_VERSION < 3 || \
63      __W32API_MAJOR_VERSION == 3 && __W32API_MINOR_VERSION  < 2
64 #  define SHGFP_TYPE_CURRENT 0
65 # endif
66 #endif
67
68 #if !defined(ASSOCF_INIT_IGNOREUNKNOWN) && !defined(__MINGW32__)
69 #define ASSOCF_INIT_IGNOREUNKNOWN 0
70 #endif
71
72 using namespace std;
73
74 namespace lyx {
75 namespace support {
76 namespace os {
77
78 namespace {
79
80 bool windows_style_tex_paths_ = true;
81
82 string cygdrive = "/cygdrive";
83
84 BOOL terminate_handler(DWORD event)
85 {
86         if (event == CTRL_CLOSE_EVENT
87             || event == CTRL_LOGOFF_EVENT
88             || event == CTRL_SHUTDOWN_EVENT) {
89                 ::raise(SIGTERM);
90                 // Relinquish our time slice.
91                 Sleep(0);
92                 return TRUE;
93         }
94         return FALSE;
95 }
96
97 } // namespace anon
98
99 void init(int /* argc */, char * argv[])
100 {
101         /* Note from Angus, 17 Jan 2005:
102          *
103          * The code below is taken verbatim from Ruurd's original patch
104          * porting LyX to Win32.
105          *
106          * Windows allows us to define LyX either as a console-based app
107          * or as a GUI-based app. Ruurd decided to define LyX as a
108          * console-based app with a "main" function rather than a "WinMain"
109          * function as the point of entry to the program, but to
110          * immediately close the console window that Windows helpfully
111          * opens for us. Doing so allows the user to see all of LyX's
112          * debug output simply by running LyX from a DOS or MSYS-shell
113          * prompt.
114          *
115          * The alternative approach is to define LyX as a genuine
116          * GUI-based app, with a "WinMain" function as the entry point to the
117          * executable rather than a "main" function, so:
118          *
119          * #if defined (_WIN32)
120          * # define WIN32_LEAN_AND_MEAN
121          * # include <stdlib.h>  // for __argc, __argv
122          * # include <windows.h> // for WinMain
123          * #endif
124          *
125          * // This will require the "-mwindows" flag when linking with
126          * // gcc under MinGW.
127          * // For MSVC, use "/subsystem:windows".
128          * #if defined (_WIN32)
129          * int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
130          * {
131          *     return mymain(__argc, __argv);
132          * }
133          * #endif
134          *
135          * where "mymain" is just a renamed "main".
136          *
137          * However, doing so means that the lyxerr messages would mysteriously
138          * disappear. They could be resurrected with something like:
139          *
140          * #ifdef WIN32
141          *  AllocConsole();
142          *  freopen("conin$","r",stdin);
143          *  freopen("conout$","w",stdout);
144          *  freopen("conout$","w",stderr);
145          * #endif
146          *
147          * This code could be invoked (say) the first time that lyxerr
148          * is called. However, Ruurd has tried this route and found that some
149          * shell scripts failed, for mysterious reasons...
150          *
151          * I've chosen for now, therefore, to simply add Ruurd's original
152          * code as-is. A wrapper program hidecmd.c has been added to
153          * development/Win32 which hides the console window of lyx when
154          * lyx is invoked as a parameter of hidecmd.exe.
155          */
156
157         // If cygwin is detected, query the cygdrive prefix
158         HKEY regKey;
159         char buf[MAX_PATH];
160         DWORD bufSize = sizeof(buf);
161         LONG retVal;
162
163         retVal = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
164                         "Software\\Cygnus Solutions\\Cygwin\\mounts v2",
165                         0, KEY_QUERY_VALUE, &regKey);
166         if (retVal != ERROR_SUCCESS) {
167                 retVal = RegOpenKeyEx(HKEY_CURRENT_USER,
168                                 "Software\\Cygnus Solutions\\Cygwin\\mounts v2",
169                                 0, KEY_QUERY_VALUE, &regKey);
170         }
171         if (retVal == ERROR_SUCCESS) {
172                 retVal = RegQueryValueEx(regKey, "cygdrive prefix", NULL, NULL,
173                                 (LPBYTE) buf, &bufSize);
174                 RegCloseKey(regKey);
175                 if ((retVal == ERROR_SUCCESS) && (bufSize <= MAX_PATH))
176                         cygdrive = rtrim(string(buf), "/");
177         }
178
179         // Catch shutdown events.
180         SetConsoleCtrlHandler((PHANDLER_ROUTINE)terminate_handler, TRUE);
181 }
182
183
184 string current_root()
185 {
186         // _getdrive returns the current drive (1=A, 2=B, and so on).
187         char const drive = ::_getdrive() + 'A' - 1;
188         return string(1, drive) + ":/";
189 }
190
191
192 bool isFilesystemCaseSensitive()
193 {
194         return false;
195 }
196
197
198 docstring::size_type common_path(docstring const & p1, docstring const & p2)
199 {
200         size_t i = 0;
201         size_t const p1_len = p1.length();
202         size_t const p2_len = p2.length();
203         while (i < p1_len && i < p2_len && uppercase(p1[i]) == uppercase(p2[i]))
204                 ++i;
205         if ((i < p1_len && i < p2_len)
206             || (i < p1_len && p1[i] != '/' && i == p2_len)
207             || (i < p2_len && p2[i] != '/' && i == p1_len))
208         {
209                 if (i)
210                         --i;     // here was the last match
211                 while (i && p1[i] != '/')
212                         --i;
213         }
214         return i;
215 }
216
217
218 bool path_prefix_is(string const & path, string const & pre)
219 {
220         return path_prefix_is(const_cast<string &>(path), pre, CASE_UNCHANGED);
221 }
222
223
224 bool path_prefix_is(string & path, string const & pre, path_case how)
225 {
226         docstring const p1 = from_utf8(path);
227         docstring const p2 = from_utf8(pre);
228         docstring::size_type const p1_len = p1.length();
229         docstring::size_type const p2_len = p2.length();
230         docstring::size_type common_len = common_path(p1, p2);
231
232         if (p2[p2_len - 1] == '/' && p1_len != p2_len)
233                 ++common_len;
234
235         if (common_len != p2_len)
236                 return false;
237
238         if (how == CASE_ADJUSTED && !prefixIs(path, pre)) {
239                 if (p1_len < common_len)
240                         path = to_utf8(p2.substr(0, p1_len));
241                 else
242                         path = to_utf8(p2 + p1.substr(common_len,
243                                                         p1_len - common_len));
244         }
245
246         return true;
247 }
248
249
250 string external_path(string const & p)
251 {
252         string const dos_path = subst(p, "/", "\\");
253
254         LYXERR(Debug::LATEX, "<Win32 path correction> ["
255                 << p << "]->>[" << dos_path << ']');
256         return dos_path;
257 }
258
259
260 static string const get_long_path(string const & short_path)
261 {
262         // GetLongPathName needs the path in file system encoding.
263         // We can use to_local8bit, since file system encoding and the
264         // local 8 bit encoding are identical on windows.
265         vector<char> long_path(MAX_PATH);
266         DWORD result = GetLongPathName(to_local8bit(from_utf8(short_path)).c_str(),
267                                        &long_path[0], long_path.size());
268
269         if (result > long_path.size()) {
270                 long_path.resize(result);
271                 result = GetLongPathName(short_path.c_str(),
272                                          &long_path[0], long_path.size());
273                 LASSERT(result <= long_path.size(), /**/);
274         }
275
276         return (result == 0) ? short_path : to_utf8(from_filesystem8bit(&long_path[0]));
277 }
278
279
280 string internal_path(string const & p)
281 {
282         return subst(get_long_path(p), "\\", "/");
283 }
284
285
286 string external_path_list(string const & p)
287 {
288         return subst(p, '/', '\\');
289 }
290
291
292 string internal_path_list(string const & p)
293 {
294         return subst(p, '\\', '/');
295 }
296
297
298 string latex_path(string const & p)
299 {
300         // We may need a posix style path or a windows style path (depending
301         // on windows_style_tex_paths_), but we use always forward slashes,
302         // since it gets written into a .tex file.
303
304         if (!windows_style_tex_paths_ && FileName::isAbsolute(p)) {
305                 string const drive = p.substr(0, 2);
306                 string const cygprefix = cygdrive + "/" + drive.substr(0, 1);
307                 string const cygpath = subst(subst(p, '\\', '/'), drive, cygprefix);
308                 LYXERR(Debug::LATEX, "<Path correction for LaTeX> ["
309                         << p << "]->>[" << cygpath << ']');
310                 return cygpath;
311         }
312         return subst(p, '\\', '/');
313 }
314
315
316 bool is_valid_strftime(string const & p)
317 {
318         string::size_type pos = p.find_first_of('%');
319         while (pos != string::npos) {
320                 if (pos + 1 == string::npos)
321                         break;
322                 if (!containsOnly(p.substr(pos + 1, 1),
323                         "aAbBcdfHIjmMpSUwWxXyYzZ%"))
324                         return false;
325                 if (pos + 2 == string::npos)
326                       break;
327                 pos = p.find_first_of('%', pos + 2);
328         }
329         return true;
330 }
331
332
333 // returns a string suitable to be passed to popen when
334 // reading a pipe
335 char const * popen_read_mode()
336 {
337         return "r";
338 }
339
340
341 string const & nulldev()
342 {
343         static string const nulldev_ = "nul";
344         return nulldev_;
345 }
346
347
348 bool is_terminal(io_channel channel)
349 {
350         switch (channel) {
351         case STDIN:
352                 if (GetStdHandle(STD_INPUT_HANDLE) == NULL)
353                         return false;
354                 break;
355         case STDOUT:
356                 if (GetStdHandle(STD_OUTPUT_HANDLE) == NULL)
357                         return false;
358                 break;
359         case STDERR:
360                 if (GetStdHandle(STD_ERROR_HANDLE) == NULL)
361                         return false;
362                 break;
363         }
364         return true;
365 }
366
367
368 shell_type shell()
369 {
370         return CMD_EXE;
371 }
372
373
374 char path_separator()
375 {
376         return ';';
377 }
378
379
380 void windows_style_tex_paths(bool use_windows_paths)
381 {
382         windows_style_tex_paths_ = use_windows_paths;
383 }
384
385
386 GetFolderPath::GetFolderPath()
387         : folder_module_(0),
388           folder_path_func_(0)
389 {
390         folder_module_ = LoadLibrary("shfolder.dll");
391         if (!folder_module_) {
392                 throw ExceptionMessage(ErrorException, _("System file not found"),
393                         _("Unable to load shfolder.dll\nPlease install."));
394         }
395
396         folder_path_func_ = reinterpret_cast<function_pointer>(::GetProcAddress(folder_module_, "SHGetFolderPathA"));
397         if (folder_path_func_ == 0) {
398                 throw ExceptionMessage(ErrorException, _("System function not found"),
399                         _("Unable to find SHGetFolderPathA in shfolder.dll\n"
400                           "Don't know how to proceed. Sorry."));
401         }
402 }
403
404
405 GetFolderPath::~GetFolderPath()
406 {
407         if (folder_module_)
408                 FreeLibrary(folder_module_);
409 }
410
411
412 // Given a folder ID, returns the folder name (in unix-style format).
413 // Eg CSIDL_PERSONAL -> "C:/Documents and Settings/USERNAME/My Documents"
414 string const GetFolderPath::operator()(folder_id _id) const
415 {
416         char folder_path[MAX_PATH];
417
418         int id = 0;
419         switch (_id) {
420         case PERSONAL:
421                 id = CSIDL_PERSONAL;
422                 break;
423         case APPDATA:
424                 id = CSIDL_APPDATA;
425                 break;
426         default:
427                 LASSERT(false, /**/);
428         }
429         HRESULT const result = (folder_path_func_)(0, id, 0,
430                                                    SHGFP_TYPE_CURRENT,
431                                                    folder_path);
432         return (result == 0) ? os::internal_path(to_utf8(from_filesystem8bit(folder_path))) : string();
433 }
434
435
436 bool canAutoOpenFile(string const & ext, auto_open_mode const mode)
437 {
438         if (ext.empty())
439                 return false;
440
441         string const full_ext = "." + ext;
442
443         DWORD bufSize = MAX_PATH + 100;
444         TCHAR buf[MAX_PATH + 100];
445         // reference: http://msdn.microsoft.com/en-us/library/bb773471.aspx
446         char const * action = (mode == VIEW) ? "open" : "edit";
447         return S_OK == AssocQueryString(ASSOCF_INIT_IGNOREUNKNOWN,
448                 ASSOCSTR_EXECUTABLE, full_ext.c_str(), action, buf, &bufSize);
449 }
450
451
452 bool autoOpenFile(string const & filename, auto_open_mode const mode)
453 {
454         // reference: http://msdn.microsoft.com/en-us/library/bb762153.aspx
455         char const * action = (mode == VIEW) ? "open" : "edit";
456         return reinterpret_cast<int>(ShellExecute(NULL, action,
457                 to_local8bit(from_utf8(filename)).c_str(), NULL, NULL, 1)) > 32;
458 }
459
460
461 string real_path(string const & path)
462 {
463         // See http://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx
464         HANDLE hpath = CreateFile(subst(path, '/', '\\').c_str(), GENERIC_READ,
465                                 FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
466
467         if (hpath == INVALID_HANDLE_VALUE) {
468                 // The file cannot be accessed.
469                 return FileName::fromFilesystemEncoding(path).absFilename();
470         }
471
472         // Get the file size.
473         DWORD size_hi = 0;
474         DWORD size_lo = GetFileSize(hpath, &size_hi);
475
476         if (size_lo == 0 && size_hi == 0) {
477                 // A zero-length file cannot be mapped.
478                 CloseHandle(hpath);
479                 return FileName::fromFilesystemEncoding(path).absFilename();
480         }
481
482         // Create a file mapping object.
483         HANDLE hmap = CreateFileMapping(hpath, NULL, PAGE_READONLY, 0, 1, NULL);
484
485         if (!hmap) {
486                 CloseHandle(hpath);
487                 return FileName::fromFilesystemEncoding(path).absFilename();
488         }
489
490         // Create a file mapping to get the file name.
491         void * pmem = MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, 1);
492
493         if (!pmem) {
494                 CloseHandle(hmap);
495                 CloseHandle(hpath);
496                 return FileName::fromFilesystemEncoding(path).absFilename();
497         }
498
499         TCHAR realpath[MAX_PATH + 1];
500
501         if (!GetMappedFileName(GetCurrentProcess(), pmem, realpath, MAX_PATH)) {
502                 UnmapViewOfFile(pmem);
503                 CloseHandle(hmap);
504                 CloseHandle(hpath);
505                 return FileName::fromFilesystemEncoding(path).absFilename();
506         }
507
508         // Translate device name to UNC prefix or drive letters.
509         TCHAR tmpbuf[MAX_PATH] = TEXT("\\Device\\Mup\\");
510         UINT namelen = _tcslen(tmpbuf);
511         if (_tcsnicmp(realpath, tmpbuf, namelen) == 0) {
512                 // UNC path
513                 _snprintf(tmpbuf, MAX_PATH, "\\\\%s", realpath + namelen);
514                 strncpy(realpath, tmpbuf, MAX_PATH);
515                 realpath[MAX_PATH] = '\0';
516         } else if (GetLogicalDriveStrings(MAX_PATH - 1, tmpbuf)) {
517                 // Check whether device name corresponds to some local drive.
518                 TCHAR name[MAX_PATH];
519                 TCHAR drive[3] = TEXT(" :");
520                 bool found = false;
521                 TCHAR * p = tmpbuf;
522                 do {
523                         // Copy the drive letter to the template string
524                         drive[0] = *p;
525                         // Look up each device name
526                         if (QueryDosDevice(drive, name, MAX_PATH)) {
527                                 namelen = _tcslen(name);
528                                 if (namelen < MAX_PATH) {
529                                         found = _tcsnicmp(realpath, name, namelen) == 0;
530                                         if (found) {
531                                                 // Repl. device spec with drive
532                                                 TCHAR tempfile[MAX_PATH];
533                                                 _snprintf(tempfile,
534                                                         MAX_PATH,
535                                                         "%s%s",
536                                                         drive,
537                                                         realpath + namelen);
538                                                 strncpy(realpath,
539                                                         tempfile,
540                                                         MAX_PATH);
541                                                 realpath[MAX_PATH] = '\0';
542                                         }
543                                 }
544                         }
545                         // Advance p to the next NULL character.
546                         while (*p++) ;
547                 } while (!found && *p);
548         }
549         UnmapViewOfFile(pmem);
550         CloseHandle(hmap);
551         CloseHandle(hpath);
552         string const retpath = subst(string(realpath), '\\', '/');
553         return FileName::fromFilesystemEncoding(retpath).absFilename();
554 }
555
556 } // namespace os
557 } // namespace support
558 } // namespace lyx