wwine 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. data/COPYING +676 -0
  2. data/README +11 -0
  3. data/wwine +970 -0
  4. data/wwine.1 +198 -0
  5. metadata +64 -0
data/README ADDED
@@ -0,0 +1,11 @@
1
+ wwine (wrapped wine) is a a wine wrapper. It wraps various flavours of wine
2
+ (including vanilla wine and crossover office/games) into a single
3
+ unified interface, complete with full bottle support for all
4
+ of them (including vanilla wine).
5
+
6
+ It integrates well with all flavours, so for instance applications
7
+ installed using crossover will be managable through the usual crossover
8
+ interface.
9
+
10
+ For vanilla wine it uses WINEPREFIX to achieve bottle support,
11
+ creating bottles as ~/.wwinebottles/[BOTTLE_NAME]
data/wwine ADDED
@@ -0,0 +1,970 @@
1
+ #!/usr/bin/ruby
2
+ # wwine
3
+ # Copyright (C) Eskild Hustvedt 2009, 2010
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ # Command-line parsing
19
+ require 'getoptlong'
20
+ # To read info for --debuginfo
21
+ require 'open3'
22
+ # Application version
23
+ $version = 0.1
24
+ # The wine flavour to use
25
+ $wine = nil
26
+ # The bottle to use
27
+ $bottle = nil
28
+ # Verbose mode (true/false)
29
+ $verbose = false
30
+ # CX path
31
+ $cxPath = nil
32
+ # Path to write wrapper to
33
+ $wrapperPath = nil
34
+ # Directory to cd to in a wrapper
35
+ $wrapperCwd = nil
36
+ # File to load wwine data from
37
+ $wwineDataFrom = nil
38
+
39
+ # Purpose: Attempt to kill all running wine processes
40
+ def killWine (dryRun = false, signal = 15)
41
+ # Open a pipe to ps to read processes
42
+ wines = IO.popen('ps uxw')
43
+ # True if we have killed something
44
+ killed = false
45
+ # The name of the signal being sent
46
+ sigName = 'SIGTERM'
47
+ # Get an integer version of signal parameter
48
+ begin
49
+ signal = Integer(signal)
50
+ rescue
51
+ signal = nil
52
+ end
53
+ # If the signal parameter is invalid, error out
54
+ if signal == nil
55
+ signal = 15
56
+ elsif signal > 15 || signal < 0
57
+ puts('Argument to --kill must be an integer between 0-15 (or none at all)')
58
+ exit(1)
59
+ end
60
+ # Map of number => name
61
+ sigNames = { 9 => 'SIGKILL', 15 => 'SIGTERM', 2 => 'SIGINT' }
62
+ if sigNames[signal] != nil
63
+ sigName = sigNames[signal]
64
+ else
65
+ sigName = 'signal '+signal
66
+ end
67
+ # Go through each line in the 'ps' output
68
+ wines.each do |line|
69
+ line.chomp!
70
+ # Ignore the header
71
+ if line =~ /USER\s*PID/
72
+ next
73
+ end
74
+ pid = String.new(line)
75
+ name = String.new(line)
76
+ # Parse the PID
77
+ pid.sub!(/^\S+\s*(\d+)\s+.*/,'\1')
78
+ # Parse the name
79
+ name.sub!(/^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+/,'')
80
+ # If either of these are true some part of the parsing failed, and we
81
+ # should thus simply skip this line
82
+ if pid == name || pid == line || name == line
83
+ next
84
+ end
85
+ # Ignore processes that are not wine-related
86
+ if name =~ /(wine|windows|[a-z]:\\|\.(exe|dll))/i
87
+ # If wine is not a part of the process name, check if the
88
+ # name either includes a \ (which most likely means it is a windows
89
+ # path because it has already matched one of the above) or exe/dll.
90
+ if ! name =~ /wine/i && ( name =~ /\// || ! name =~ /\.(exe|dll)/i )
91
+ next
92
+ end
93
+ else
94
+ next
95
+ end
96
+ # At least on Linux kernels, /proc/ will include useful info, we use it to
97
+ # check if the exe actually is wine
98
+ if File.exists?('/proc/'+pid+'/exe') and File.symlink?('/proc/'+pid+'/exe')
99
+ linkValue = File.readlink('/proc/'+pid+'/exe')
100
+ if File.readlink('/proc/'+pid+'/exe') !~ /wine/i
101
+ next
102
+ end
103
+ end
104
+ # Clean up the name
105
+ name.sub!(/\s*$/,'')
106
+ # Send SIGTERM (or output information if in dryRun mode)
107
+ if !dryRun
108
+ puts 'Sending '+sigName+' to '+name+' (PID '+pid+')'
109
+ begin
110
+ Process.kill(signal,pid.to_i)
111
+ rescue
112
+ puts('Error while attempting to send signal to '+pid+': '+$!)
113
+ end
114
+ else
115
+ puts 'Would send '+sigName+' to '+name+' (PID '+pid+')'
116
+ end
117
+ killed = true
118
+ end
119
+ if ! killed
120
+ puts 'No wine processes found.'
121
+ end
122
+ end
123
+
124
+ # Purpose: Print formatted --help output
125
+ # Usage: printHelp('-shortoption', '--longoption', 'description');
126
+ # Description will be reformatted to fit within a normal terminal
127
+ def printHelp (short,long,description)
128
+ maxlen = 80
129
+ optionlen = 20
130
+ # Check if the short/long are LONGER than optionlen, if so, we need
131
+ # to do some additional magic to take up only $maxlen.
132
+ # The +1 here is because we always add a space between them, no matter what
133
+ if (short.length + long.length + 1) > optionlen
134
+ optionlen = short.length + long.length + 1;
135
+ end
136
+
137
+ generatedDesc = ''
138
+ currdesc = ''
139
+
140
+ description.split(/ /).each do |part|
141
+ if(generatedDesc.length > 0)
142
+ if (currdesc.length + part.length + 1 + 20) > maxlen
143
+ generatedDesc.concat("\n")
144
+ currdesc = ''
145
+ else
146
+ currdesc.concat(' ')
147
+ generatedDesc.concat(' ')
148
+ end
149
+ end
150
+ currdesc.concat(part)
151
+ generatedDesc.concat(part)
152
+ end
153
+ if !(generatedDesc.length > 0)
154
+ raise("Option mismatch")
155
+ end
156
+ generatedDesc.split(/\n/).each do |descr|
157
+ printf("%-4s %-15s %s\n",short,long,descr)
158
+ short = ''; long = ''
159
+ end
160
+ end
161
+
162
+ # Purpose: Quote a string (or array of strings) for use in the shell
163
+ def shellQuote(target)
164
+ if target.class == Array
165
+ newTarget = []
166
+ target.each do |subt|
167
+ subt = shellQuote(subt)
168
+ newTarget.push(subt)
169
+ end
170
+ return newTarget
171
+ end
172
+
173
+ fixedTarget = target.gsub('\\') { '\\\\' }
174
+ fixedTarget.gsub!(/'/,"\\\\'")
175
+
176
+ finalTarget = "'"
177
+ finalTarget.concat(fixedTarget)
178
+ finalTarget.concat("'");
179
+ return finalTarget
180
+ end
181
+
182
+ # Purpose: Unquote a string generated by shellQuote
183
+ def unShellQuote(target)
184
+ fixedTarget = target.gsub("\\'","'")
185
+ fixedTarget.gsub!('\\\\') { '\\' }
186
+ return fixedTarget
187
+ end
188
+
189
+ # Purpose: Unquote a full line of shellQuoted strings
190
+ def unquoteShellArray (source,targetarr)
191
+ source.split(/ '/).each do |part|
192
+ part.sub!(/'$/,'')
193
+ part.sub!(/^'/,'')
194
+ part.chomp!
195
+ if part.match(/\S/)
196
+ targetarr.push(unShellQuote(part))
197
+ end
198
+ end
199
+ return targetarr
200
+ end
201
+
202
+ # Purpose: Write a wrapper script
203
+ def writeWrapper (targetFile, wwineCommand, otherCommand, cwd, wwineInfo, args, wineprefix = nil)
204
+ # Open the file for riting
205
+ begin
206
+ file = File.open(targetFile,'w')
207
+ rescue
208
+ puts('Failed to open "'+targetFile+'" for writing: '+$!)
209
+ exit(1)
210
+ end
211
+
212
+ file.puts('#!/bin/sh')
213
+ file.puts('# Script autogenerated by wwine '+$version.to_s+' (http://random.zerodogg.org/wwine)')
214
+ file.puts('# Licensed under the GNU General Public License version 3 or later')
215
+ file.puts('#')
216
+ file.puts('# You should have received a copy of the GNU General Public License')
217
+ file.puts('# along with wwine. If not, see <http://www.gnu.org/licenses/>.')
218
+ file.puts('#')
219
+ file.puts('# The following two lines contain wwine metadata');
220
+ file.puts('# wwineInfo: v1 '+shellQuote(wwineInfo).join(' '))
221
+ file.puts('# wwineCmd: '+shellQuote(args).join(' '));
222
+ file.puts('')
223
+ file.puts("cd "+shellQuote(cwd)+" || exit 1")
224
+ file.puts('wwine='+shellQuote(File.expand_path($0)))
225
+ file.puts('if [ ! -x "$wwine" ]; then')
226
+ file.puts("\t"+'wwine="`which wwine 2> /dev/null`"')
227
+ file.puts('fi')
228
+ file.puts('if [ "x$wwine" != "x" ]; then')
229
+ file.puts("\t"+'exec "$wwine" '+shellQuote(wwineCommand).join(" ")+' "$@"')
230
+ file.puts('else')
231
+ # Add wineprefix if needed. This is used when --wine is wine because wine
232
+ # has no support for specifying a bottle on the command-line.
233
+ if wineprefix != nil
234
+ wineprefix.sub!(/'/,'\\')
235
+ file.puts("\t"+'export WINEPREFIX='+shellQuote(wineprefix))
236
+ file.puts("\t"+"export WINEDEBUG='-all'");
237
+ end
238
+ file.puts("\t"+"exec "+shellQuote(otherCommand).join(" ")+' "$@"')
239
+ file.puts('fi')
240
+ file.puts('echo "Launch script failed. No wwine found and wine command failed to exec."')
241
+ file.puts('exit 1')
242
+
243
+ file.chmod(0755)
244
+ file.close
245
+
246
+ puts('Wrote wrapper script to '+targetFile)
247
+ end
248
+
249
+ # Purpose: Prepare to generate a wrapper script
250
+ def generateWrapper (wine,bottle,targetFile,wineCommand,args,type = nil)
251
+ wwineCmd = []
252
+ wwineInfo = ['nil','nil','nil','nil']
253
+
254
+ if File.exists?(targetFile)
255
+ puts(targetFile+': already exists. Bailing out')
256
+ puts('Remove the target file first, then try again')
257
+ exit(1)
258
+ end
259
+
260
+ # Generate a wwine command-line
261
+ if $verbose
262
+ wwineCmd.push('--verbose')
263
+ end
264
+ if $cxPath
265
+ wwineCmd.push('--cxinstalldir',$cxPath)
266
+ wwineInfo[3] = $cxPath
267
+ end
268
+ if wine
269
+ wwineCmd.push('--wine',wine)
270
+ wwineInfo[1] = wine
271
+ end
272
+ if bottle
273
+ wwineCmd.push('--bottle',bottle)
274
+ wwineInfo[2] = bottle;
275
+ end
276
+ # Append -- if it isn't already
277
+ if ! args.join(' ').match(/ -- /)
278
+ wwineCmd.push('--')
279
+ end
280
+
281
+ wwineCmd.concat(args)
282
+
283
+ if $wrapperCwd == nil
284
+ $wrapperCwd = Dir.pwd
285
+ end
286
+
287
+ wwineInfo[0] = $wrapperCwd
288
+
289
+ wineprefix = nil
290
+
291
+ # For wine there is no --bottle definition in the wine command itself,
292
+ # so we need to export a wineprefix in the launch script
293
+ if type == 'wine'
294
+ wineprefix = ENV['WINEPREFIX']
295
+ end
296
+
297
+ writeWrapper(targetFile ,wwineCmd, wineCommand, $wrapperCwd, wwineInfo, args, wineprefix)
298
+ end
299
+
300
+ # Purpose: Load wwine data from a wrapper script
301
+ def loadWwineDataFromFile (file,dryRun = false)
302
+ begin
303
+ puts 'Loading settings from '+file+':'
304
+ outFormat = '%-20s: %s'+"\n"
305
+ source = File.open(file)
306
+
307
+ wwineInfo = ''
308
+ wwineCmd = ''
309
+
310
+ source.each do |line|
311
+ if line.sub!(/^#\s+wwineInfo:\s*/,'')
312
+ wwineInfo = line
313
+ elsif line.sub!(/^#\s+wwineCmd:\s*/,'')
314
+ wwineCmd = line
315
+ end
316
+ if wwineInfo != '' && wwineCmd != ''
317
+ break
318
+ end
319
+ end
320
+
321
+ info = []
322
+ cmd = []
323
+
324
+ if wwineInfo == nil || wwineCmd == nil || wwineInfo == '' || wwineCmd == ''
325
+ puts "The wwine header in this file is broken or missing."
326
+ puts "Unable to continue."
327
+ exit(0)
328
+ end
329
+
330
+ unquoteShellArray(wwineInfo,info)
331
+ unquoteShellArray(wwineCmd,cmd)
332
+
333
+ if info[0] != 'v1'
334
+ puts "This version of wwine only supports info format v1."
335
+ puts "This file is verison "+info[0]
336
+ puts "Unable to continue."
337
+ exit(0)
338
+ end
339
+
340
+ if wwineInfo.length < 6 || wwineCmd.length < 1
341
+ puts "The wwine header in this file is broken."
342
+ puts "Unable to continue."
343
+ exit(0)
344
+ end
345
+
346
+ if info[1] != 'nil'
347
+ printf(outFormat,'Switching to directory',info[1])
348
+ if File.exists?(info[1])
349
+ Dir.chdir(info[1])
350
+ else
351
+ puts info[1]+': does not exist, giving up.'
352
+ exit(0)
353
+ end
354
+ end
355
+
356
+ if $wine == nil && info[2] != 'nil'
357
+ $wine = info[2]
358
+ printf(outFormat,'Using --wine',$wine)
359
+ end
360
+
361
+ if $bottle == nil && info[3] != 'nil'
362
+ $bottle = info[3]
363
+ printf(outFormat,'Using --bottle',$bottle)
364
+ end
365
+
366
+ if $cxPath == nil && info[4] != 'nil'
367
+ $cxPath = info[4]
368
+ printf(outFormat,'Using --cxinstalldir',$cxPath)
369
+ end
370
+
371
+ if ! dryRun
372
+ printf(outFormat,'Will now run',cmd.join(' '))
373
+ ARGV.unshift(*cmd)
374
+ end
375
+ rescue => ex
376
+ handleException(ex)
377
+ end
378
+ end
379
+
380
+ # Purpose: Run a command, outputting info of it in verbose mode
381
+ def runcmd(cmd, type = 'exec')
382
+ if(type == 'exec')
383
+ vputs 'Executing: '+cmd.join(' ')
384
+ exec *cmd
385
+ else
386
+ vputs 'Running: '+cmd.join(' ')
387
+ return system(*cmd)
388
+ end
389
+ end
390
+
391
+ # Purpose: Check for a file in path
392
+ def inPath(exec)
393
+ ENV['PATH'].split(/:/).each do |part|
394
+ if File.executable?(part+'/'+exec) and not File.directory?(part+'/'+exec)
395
+ return true
396
+ end
397
+ end
398
+ return false
399
+ end
400
+
401
+ # Purpose: Output a string if in verbose mode
402
+ def vputs (str)
403
+ if $verbose
404
+ puts(str)
405
+ end
406
+ end
407
+
408
+ # Purpose: Print the help output
409
+ def Help ()
410
+ puts "wwine "+$version.to_s
411
+ puts ""
412
+ puts "Usage: wwine (WWINE PARAMETERS) PROGRAM -- [PROGRAM ARGUMENTS]"
413
+ puts ""
414
+ printHelp('-h','--help','Display this help text')
415
+ printHelp('-w','--wine','Select the wine to use: wine, cxoffice, cxgames, cedega, or a path to a wine bin. Default: wine')
416
+ printHelp('-b','--bottle','Use the selected bottle (~/.wwinebottles/[NAME] for wine, CX bottle or cedega folder, depending on the --wine in use). The bottle will be created if it does not exist.')
417
+ printHelp('-k','--kill','Attempt to kill all running wine processes')
418
+ printHelp('','--drykill','Print what --kill would have done, but don\'t actually do anything')
419
+ printHelp('-c','--cxinstalldir','Use the supplied path as the install path for CXoffice/CXgames (default: autodetect).')
420
+ printHelp('','--wrap','Write a wrapper script of your current wwine command to the path supplied')
421
+ printHelp('','--wrapdir','Use the supplied directory as the working directory for the script created by --wrap (default: current directory)')
422
+ printHelp('-s','--from','Load parameters and program from the wrapper script supplied. Command-line arguments overrides settings from wrapper script.')
423
+ printHelp('-v','--verbose','Enable verbose mode')
424
+ printHelp('','--man','Show the wwine manpage')
425
+ end
426
+
427
+ # Purpose: Show the manpage
428
+ def showManPage ()
429
+ if ! inPath('man')
430
+ puts
431
+ puts "You don't appear to have the 'man' program installed."
432
+ puts "Please install it, then re-run wwine --man"
433
+ exit(0)
434
+ end
435
+ mySelf = File.expand_path($0)
436
+ while File.symlink?(mySelf)
437
+ mySelf = File.readlink(mySelf)
438
+ end
439
+ sourceDir = '.'
440
+ if mySelf != nil
441
+ sourceDir = File.dirname(mySelf)
442
+ end
443
+ dirs = [sourceDir]
444
+ if ENV['MANPATH']
445
+ dirs.concat(ENV['MANPATH'].split(':'))
446
+ end
447
+ dirs.push('./')
448
+ dirs.each do |dir|
449
+ [ 'wwine.1','man1/wwine.1','man1/wwine.1.gz','man1/wwine.1.bz2','man1/wwine.1.lzma'].each do |manFile|
450
+ if File.exists?(dir+'/'+manFile)
451
+ exec('man',dir+'/'+manFile)
452
+ end
453
+ end
454
+ end
455
+ puts
456
+ puts 'wwine failed to locate its manpage.'
457
+ puts 'Run the following command to view it:'
458
+ puts '\curl -s "http://github.com/zerodogg/wwine/raw/master/wwine.1" | groff -T utf8 -man | \less'
459
+ end
460
+
461
+ # Purpose: Get wine version
462
+ def getWineVersion (source = 'wine', fromStderr = false, cmdOpt = '--version')
463
+ wineVer = nil
464
+
465
+ # Don't try to get it if we have no wine to run
466
+ if inPath(source) || File.executable?(source)
467
+ begin
468
+ cmd = [ source,cmdOpt ]
469
+ Open3.popen3(*cmd) { |stdin, stdout, stderr|
470
+ # Fetch the line from either stderr or out
471
+ if fromStderr
472
+ wineVer = stderr.gets(nil)
473
+ else
474
+ wineVer = stdout.gets(nil)
475
+ end
476
+ }
477
+ # Get the actual version number by stripping away any leading nondigit text
478
+ wineVer.sub!(/^\D+/,'')
479
+ wineVer.chomp!
480
+ rescue
481
+ wineVer = nil
482
+ end
483
+ end
484
+ return wineVer
485
+ end
486
+
487
+ # Purpose: Get cedega version string
488
+ def getCedegaVersionString ()
489
+ cedegaVer = nil
490
+
491
+ if inPath('cedega')
492
+ begin
493
+ # We need the HOME variable
494
+ if ENV.has_key?('HOME')
495
+ # The cedega RC file
496
+ rcFile = ENV['HOME']+'/.cedega/.cedegarc'
497
+ # The cedega build number file
498
+ uiBuildFile = ENV['HOME']+'/.cedega/.ui/BUILDNUMBER'
499
+ # If we have the rc file, open it and fetch the current default
500
+ # engine version from the 'default' setting. It is the closest we are
501
+ # going to get to a current version number without jumping through a lot
502
+ # of hoops.
503
+ if File.exists?(rcFile)
504
+ ind = File.open(rcFile)
505
+ ind.each do |line|
506
+ if line.match(/^default=/)
507
+ line.sub!(/^default=/,'')
508
+ line.chomp!
509
+ cedegaVer = line
510
+ break
511
+ end
512
+ end
513
+ end
514
+ # If we have a default ver, fetch the UI version as well, as we have to pass
515
+ # through the UI, that number can be useful
516
+ if cedegaVer != nil && File.exists?(uiBuildFile)
517
+ ind = File.open(uiBuildFile)
518
+ buildNo = ind.gets(nil)
519
+ buildNo.chomp!
520
+ if buildNo != nil
521
+ cedegaVer.concat(' (UI build '+buildNo+')');
522
+ end
523
+ end
524
+ end
525
+ rescue
526
+ end
527
+ if cedegaVer == nil
528
+ cedegaVer = 'present'
529
+ end
530
+ else
531
+ cedegaVer = '(not present)'
532
+ end
533
+
534
+ return cedegaVer
535
+ end
536
+
537
+ # Purpose: Get cx* version
538
+ def getCXVersionsFrom (source,wantString = false)
539
+ cxVer = nil
540
+ cxwVer = nil
541
+ if source != nil
542
+ begin
543
+ # Store the binfile for use later
544
+ binFile = source[0]
545
+ # pop off the dummy command
546
+ source.pop
547
+ # Add version
548
+ source.push('--version')
549
+ source = source.join(' ')
550
+
551
+ out = IO.popen(source)
552
+ # The info we need is on line two
553
+ cxVer = out.readline
554
+ cxVer = out.readline
555
+ # Get the actual version number by stripping away any leading nondigit text
556
+ cxVer.sub!(/^\D+/,'')
557
+ cxVer.chomp!
558
+
559
+ cxwVer = getWineVersion(binFile.sub(/cxstart$/,'wineserver'),true,'-v')
560
+ rescue
561
+ cxwVer = nil
562
+ cxVer = nil
563
+ end
564
+ end
565
+ if wantString
566
+ # If a string has been requested, generate one and return it
567
+ if cxVer != nil
568
+ if cxwVer != nil
569
+ return cxVer+' (based on wine '+cxwVer+')'
570
+ else
571
+ return cxVer
572
+ end
573
+ else
574
+ return '(not present)'
575
+ end
576
+ else
577
+ return cxVer,cxwVer
578
+ end
579
+ end
580
+
581
+ # Purpose: Print debugging info
582
+ def debugInfo ()
583
+ outFormat = '%-20s: %s'+"\n"
584
+ puts "wwine "+$version.to_s
585
+ # Load a --from file if it is set
586
+ if $wwineDataFrom
587
+ loadWwineDataFromFile($wwineDataFrom,true)
588
+ end
589
+ # Fetch the md5sum of us. This will at least give a certain clue about the revision
590
+ # of wwine used. Primarily useful for identifying older git clones in bug reports.
591
+ begin
592
+ if inPath('md5sum')
593
+ outStr = nil
594
+ out = IO.popen('md5sum '+File.expand_path($0))
595
+ outStr = out.readline
596
+ outStr.sub!(/\s+.*$/,'')
597
+ outStr.chomp!
598
+ printf(outFormat,'wwine md5sum',outStr)
599
+ else
600
+ printf(outFormat,'wwine md5sum','(md5sum command missing)')
601
+ end
602
+ rescue
603
+ printf(outFormat,'wwine md5sum','(exception: '+$!+')');
604
+ end
605
+
606
+ begin
607
+
608
+ defaultWine,defaultWCMD = getAutoWithParams(nil,false)
609
+ if defaultWine == nil
610
+ defaultWine = '(no wine found)'
611
+ end
612
+ printf(outFormat,'Default flavour',defaultWine)
613
+
614
+ if inPath('wine')
615
+ wineVer = getWineVersion()
616
+ else
617
+ wineVer = '(not present)'
618
+ end
619
+ printf(outFormat,'Wine',wineVer)
620
+
621
+ cxgBin = getCXwithParams('cxgames',nil,false)
622
+ cxgVer = getCXVersionsFrom(cxgBin,true)
623
+ printf(outFormat,'Crossover Games',cxgVer)
624
+
625
+ cxoBin = getCXwithParams('cxoffice',nil,false)
626
+ cxoVer = getCXVersionsFrom(cxoBin,true)
627
+ printf(outFormat,'Crossover Office',cxoVer)
628
+
629
+ cedegaVer = getCedegaVersionString()
630
+ printf(outFormat,'Cedega',cedegaVer)
631
+ rescue => ex
632
+ handleException(ex)
633
+ end
634
+ exit(0)
635
+ end
636
+
637
+ # Purpose: Handle an exception
638
+ def handleException(ex)
639
+ puts('---')
640
+ puts('Exception: '+ex.to_s)
641
+ puts('Backtrace: '+"\n"+ex.backtrace.join("\n"))
642
+ puts('---')
643
+ puts()
644
+ puts('An exception has occurred and wwine can not continue.')
645
+ puts('This almost certainly reflects a bug in wwine.')
646
+ puts('Please check that you have the latest version off wwine,')
647
+ puts('and if you do, report this issue along with the text between the "---" above');
648
+ puts('to http://random.zerodogg.org/wwine/bugs')
649
+ exit(1)
650
+ end
651
+
652
+ # Purpose: Get parameters for wine
653
+ def getWineWithParams (wine,bottle, missingIsFatal = true)
654
+ final = []
655
+ possibleWines = [ 'wine32','wine','wine64','wine.bin','wine.real' ]
656
+ # Manual search path, in the odd case none of the above are in $PATH.
657
+ # Ie. /usr/lib32/wine/wine.bin is the real wine binary on Debian
658
+ searchPath = [ '/usr/lib32/wine/','/usr/bin' ]
659
+ # If wine is not a path or does not exist, check for wine in the path
660
+ if !File.exists?(wine)
661
+ wine = nil
662
+ possibleWines.each do |w|
663
+ if inPath(w)
664
+ wine = w
665
+ break
666
+ end
667
+ end
668
+
669
+ if wine == nil
670
+ searchPath.each do |p|
671
+ possibleWines.each do |w|
672
+ if File.executable?(p+'/'+w) && File.file?(p+'/'+w)
673
+ wine = p+'/'+w
674
+ break
675
+ end
676
+ end
677
+ if wine != nil
678
+ break
679
+ end
680
+ end
681
+ end
682
+
683
+ if wine == nil
684
+ if missingIsFatal == true
685
+ puts('"wine" does not appear to be installed')
686
+ exit(1)
687
+ else
688
+ return nil
689
+ end
690
+ end
691
+ end
692
+ final.push(wine)
693
+
694
+ # Set WINEPREFIX for bottle support
695
+ if(bottle != nil && bottle.length > 0)
696
+ if ENV.has_key?('WINEPREFIX') && ENV['WINEPREFIX'].length > 0
697
+ warn 'WINEPREFIX= was already set, overriding it.'
698
+ end
699
+ if ! (bottle =~ /^\.?\//)
700
+ bottle = ENV['HOME']+'/.wwinebottles/'+bottle
701
+ if !File.exists?(ENV['HOME']+'/.wwinebottles/')
702
+ vputs(ENV['HOME']+'/.wwinebottles/: does not exist, creating')
703
+ begin
704
+ Dir.mkdir(ENV['HOME']+'/.wwinebottles/')
705
+ rescue SystemCallError
706
+ puts('Failed to create directory "'+ENV['HOME']+'/.wwinebottles/'+": "+$!)
707
+ puts('Unable to continue.')
708
+ exit(1)
709
+ end
710
+ end
711
+ end
712
+ ENV['WINEPREFIX'] = bottle
713
+ vputs('Set WINEPREFIX='+bottle)
714
+ end
715
+
716
+ # Default to no debugging output if WINEDEBUG is not set yet
717
+ if ! ENV.has_key?('WINEDEBUG')
718
+ vputs 'Set WINEDEBUG=-all'
719
+ ENV['WINEDEBUG'] = '-all'
720
+ end
721
+
722
+ return final
723
+ end
724
+
725
+ # Purpose: Get parameters for crossover
726
+ def getCXwithParams (wine,bottle, missingIsFatal = true)
727
+ final = []
728
+ cxdir = nil
729
+
730
+ # Various crossover install paths
731
+ wines = ['/opt/'+wine, ENV['HOME']+'/'+wine, ENV['HOME']+'/.local/'+wine, ENV['HOME']+'/games/'+wine]
732
+
733
+ # If cxpath is set then overwrite the default paths
734
+ if $cxPath != nil
735
+ wines = [ $cxPath+'/'+wine ]
736
+ end
737
+
738
+ wines.each do |path|
739
+ if File.exists?(path) and File.executable?(path+'/bin/cxstart')
740
+ cxdir = path
741
+ break
742
+ end
743
+ end
744
+
745
+ # If we're using cxPath, perform some additional detection
746
+ if $cxPath != nil
747
+ if File.exists?($cxPath+'/etc/'+wine+'.conf')
748
+ cxdir = $cxPath
749
+ end
750
+ end
751
+
752
+ # If the dir does not exist, give up
753
+ if not cxdir
754
+ if !missingIsFatal
755
+ return nil
756
+ end
757
+ if $cxPath != nil
758
+ puts('Could not find a directory named "'+wine+'" in '+$cxPath)
759
+ puts('and '+$cxPath+' does not appear to be a '+wine+' install directory')
760
+ if wine == 'cxgames'
761
+ reverse = 'cxoffice'
762
+ else
763
+ reverse = 'cxgames'
764
+ end
765
+ if File.exists?($cxPath+'/'+reverse) || File.exists?($cxPath+'/etc/'+reverse+'.conf')
766
+ puts('The directory looks like a '+reverse+' directory. Maybe you wanted')
767
+ puts('--wine '+reverse+' instead?')
768
+ end
769
+ else
770
+ puts('Failed to locate '+wine)
771
+ puts('You could try to explicitly tell wwine where '+wine+' is installed')
772
+ puts('by supplying --cxinstalldir')
773
+ end
774
+ exit(1)
775
+ end
776
+
777
+ final.push(cxdir+'/bin/cxstart')
778
+ if(bottle != nil && bottle.length > 0)
779
+ final.push('--bottle',bottle)
780
+ end
781
+ final.push('--');
782
+
783
+ # Create the bottle if it does not exist
784
+ if bottle != nil and ! File.exists?(ENV['HOME']+'/.'+wine+'/'+bottle)
785
+ puts 'The bottle '+bottle+' did not exist, creating...'
786
+ runcmd([ cxdir+'/bin/cxbottle', '--bottle',bottle,'--create'],'system')
787
+ if ! File.exists?(ENV['HOME']+'/.'+wine+'/'+bottle)
788
+ puts 'Bottle creation failed.'
789
+ exit 1
790
+ end
791
+ end
792
+
793
+ return final
794
+ end
795
+
796
+ # Purpose: Get parameters for cedega
797
+ def getCedegaWithParams (wine,bottle, missingIsFatal = true)
798
+ final = []
799
+
800
+ if not inPath('cedega')
801
+ if missingIsFatal
802
+ puts('cedega does not appear to be installed')
803
+ exit(1)
804
+ else
805
+ return nil
806
+ end
807
+ end
808
+
809
+ final.push('cedega','--install')
810
+
811
+ # Use wwineFolder as the folder if no bottle was supplied
812
+ if (bottle == nil || bottle.length == 0)
813
+ bottle = 'wwineFolder'
814
+ end
815
+ final.push(bottle)
816
+
817
+ return final
818
+ end
819
+
820
+ # Purpose: Detect which wine to use and get parameters for that one
821
+ # Returns: type,cmd
822
+ def getAutoWithParams (bottle, missingIsFatal = true, returnFlavour = false)
823
+ cmd = getWineWithParams('wine',bottle,false)
824
+ type = 'wine'
825
+ if cmd == nil
826
+ cmd = getCXwithParams('cxgames',bottle,false)
827
+ type = 'cxgames'
828
+ end
829
+ if cmd == nil
830
+ cmd = getCXwithParams('cxoffice',bottle,false)
831
+ type = 'cxoffice'
832
+ end
833
+ if cmd == nil
834
+ cmd = getCedegaWithParams('cedega',bottle,false)
835
+ type = 'cedega'
836
+ end
837
+ if cmd == nil
838
+ if missingIsFatal
839
+ puts 'Failed to detect any wine version at all.'
840
+ exit(1)
841
+ else
842
+ return nil
843
+ end
844
+ else
845
+ return type,cmd
846
+ end
847
+ end
848
+
849
+ # Purpose: Run the wine specified, using the bottle specified and arguments specified
850
+ def runWine (wine,bottle,args)
851
+ cmd = nil
852
+ type = nil
853
+
854
+ if wine == nil
855
+ type,cmd = getAutoWithParams(bottle)
856
+ else
857
+ # Expand cxg and cxo
858
+ if wine == 'cxg'
859
+ wine = 'cxgames'
860
+ elsif wine == 'cxo'
861
+ wine = 'cxoffice'
862
+ end
863
+
864
+ type = wine
865
+
866
+ if wine == 'cxgames' || wine == 'cxoffice'
867
+ cmd = getCXwithParams(wine,bottle)
868
+ elsif wine == 'wine' || File.executable?(wine)
869
+ type = 'wine'
870
+ cmd = getWineWithParams(wine,bottle)
871
+ elsif wine == 'cedega'
872
+ cmd = getCedegaWithParams(wine,bottle)
873
+ else
874
+ puts('Unknown --wine: '+wine)
875
+ puts('Must be one of: wine, cxgames, cxoffice, cedega, or the path to a wine executable')
876
+ exit(1)
877
+ end
878
+ end
879
+ cmd.concat(args)
880
+
881
+ # If wrapperPath is set, then we're suppose to generate a wrapper script instead
882
+ # of actually executing it.
883
+ if $wrapperPath != nil
884
+ generateWrapper(wine,bottle,$wrapperPath,cmd,args,type)
885
+ else
886
+ runcmd(cmd)
887
+ end
888
+ end
889
+
890
+ opts = GetoptLong.new(
891
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
892
+ [ '--wine', '-w', GetoptLong::REQUIRED_ARGUMENT ],
893
+ [ '--bottle', '-b', GetoptLong::REQUIRED_ARGUMENT ],
894
+ [ '--kill', '-k', GetoptLong::OPTIONAL_ARGUMENT ],
895
+ [ '--drykill', GetoptLong::NO_ARGUMENT ],
896
+ [ '--cxinstalldir','-c',GetoptLong::REQUIRED_ARGUMENT ],
897
+ [ '--version',GetoptLong::NO_ARGUMENT ],
898
+ [ '--wrap', GetoptLong::REQUIRED_ARGUMENT ],
899
+ [ '--wrapdir', GetoptLong::REQUIRED_ARGUMENT ],
900
+ [ '--debuginfo', GetoptLong::NO_ARGUMENT ],
901
+ [ '--man', GetoptLong::NO_ARGUMENT ],
902
+ [ '--from', '-s', GetoptLong::REQUIRED_ARGUMENT ],
903
+ [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ]
904
+ )
905
+
906
+ # Handle command-line arguments
907
+ begin
908
+ opts.each do |opt, arg|
909
+ case opt
910
+ when '--help'
911
+ Help()
912
+ exit(0)
913
+ when '--wine'
914
+ $wine = arg
915
+ when '--bottle'
916
+ $bottle = arg
917
+ when '--verbose'
918
+ $verbose = true
919
+ when '--drykill'
920
+ killWine(true)
921
+ exit(0)
922
+ when '--debuginfo'
923
+ debugInfo()
924
+ when '--kill'
925
+ begin
926
+ killWine(false,arg)
927
+ rescue
928
+ puts "Error while attempting to kill processes: "+$!
929
+ end
930
+ exit(0)
931
+ when '--cxinstalldir'
932
+ $cxPath = arg
933
+ when '--version'
934
+ puts('wwine version '+$version.to_s+' (for wine version info, run wwine --debuginfo)')
935
+ exit(0)
936
+ when '--wrap'
937
+ $wrapperPath = arg
938
+ when '--wrapdir'
939
+ $wrapperCwd = arg
940
+ when '--from'
941
+ $wwineDataFrom = arg
942
+ when '--man'
943
+ showManPage()
944
+ exit(0)
945
+ end
946
+ end
947
+
948
+ if ENV['WWINE_VERBOSE'] != nil && ENV['WWINE_VERBOSE'] == '1'
949
+ puts('--verbose assumed because the environment variable WWINE_VERBOSE is set to 1')
950
+ $verbose = true
951
+ end
952
+ rescue
953
+ puts('See --help for more inforation')
954
+ exit(1)
955
+ end
956
+
957
+ if $wwineDataFrom
958
+ loadWwineDataFromFile($wwineDataFrom)
959
+ end
960
+
961
+ if ARGV.length == 0
962
+ Help()
963
+ exit(1)
964
+ end
965
+
966
+ begin
967
+ runWine($wine,$bottle,ARGV)
968
+ rescue => ex
969
+ handleException(ex)
970
+ end