summaryrefslogtreecommitdiff
path: root/play.sh
blob: 4ec9aff9ee24ab2af3c6abd82c80ca0a02feb085 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/bin/zsh -f

# ZSH setup {{{1

# make the $functions and $functracs variables available
zmodload -F zsh/parameter +p:functions +p:functrace

# variables gathered from the environment {{{1

# debugging
# 0 -> off (+ WINEDEBUG=-all)
# 1 -> log messages (mostly 'eval' calls) (+ WINEDEBUG=-all)
# 2 -> log messages (+ default WINEDEBUG)
# 3 -> 2 + xtrace
PLAY_DEBUG=${PLAY_DEBUG:-0}

[[ $PLAY_DEBUG == 3 ]] && setopt xtrace

# directory we are in
PLAY_DIR="${PLAY_DIR:-${0:h}}"

# directory of installed games
PLAY_GAMES="${PLAY_GAMES:-$PLAY_DIR/installed}"

# directory of templages
PLAY_TEMPLATES="${PLAY_TEMPLATES:-$PLAY_DIR/templates}"

# binary to echo
PLAY_BIN="${PLAY_BIN:-$0}"

# initialization of internal variables {{{1

# environment dictionaries
# ENV: name to value
# EENV: name to string, which is evaluated into the value
typeset -A ENV EENV

# current binary -- complete path
BIN=${0:A}

# current template -- used for EXPORT
CUR_TEMPLATE=play

# array of loaded templates
declare -a TEMPLATES

# Array of phases
PHASES=(setenv prepare setupX startX run cleanup)
declare -r PHASES
declare -A PHASE_FUNS

# global functions {{{1

# print passed arguments to stderr
# if first arg is "-n", do not append newline
out () {
    zparseopts -D n=n

    echo $n ">>> $*" >&2
}

# print passed arguments to stderr ONLY if PLAY_DEBUG is nonzero
log () {
    (( PLAY_DEBUG > 0 )) && echo "*** $*" >&2
}

# die with a supplied error message
die () {
    out "*** ERROR: $*"
    exit 1
}

# exports $1=$2
exp () {
    log "Setting envvar '$1' to '$2'"
    export $1=$2
}

# executes the passed command including arguments 
# (as individual parameters to this function)
# if first arg is "-e" use exec instead of eval
# if first arg is "-b" background the job
exc () {
    local cmd="eval" bg

    if [[ $1 == "-e" ]]; then
        cmd="exec"
        shift
    elif [[ $1 == "-b" ]]; then
        bg=" in background"
        shift
    fi

    log "Executing (using '$cmd'$bg):"
    log "> $*"

    if [[ $cmd == exec ]]; then
        exec $@
    elif [[ -n $bg ]]; then
        $* &
    else
        $*
    fi
}

# exports the given key with its value in $ENV
# NB: it also exports empty values
set_env () {
    local k=$1
    local v=${ENV[$k]}

    v=${(P)${:-PLAY_ENV_$k}:-$v}
    exp $k $v
}

# exports the given key with its value in $EENV
# this implies, that the value is evaluated!
# NB: it also exports empty values
set_eenv () {
    local k=$1
    local v=${EENV[$k]}
    v=${(P)${:-PLAY_EENV_$k}:-$v}
    exp $k `eval $v`
}

# inherits a specified template
# i.e. source it :)
# if first argument is "-e", do not die if template could not be found
inherit () {
    zparseopts -D e=nonfatal
    
    for templ; do
        local old_templ=$CUR_TEMPLATE

        CUR_TEMPLATE=$templ

        if [[ ! -e $PLAY_TEMPLATES/$templ ]]; then
            if [[ -n $nonfatal ]]; then
                log "Template '$templ' not found"
                return
            else
                die "Template '$templ' not found"
            fi
        fi

        source $PLAY_TEMPLATES/$templ

        TEMPLATES+=$CUR_TEMPLATE

        CUR_TEMPLATE=$old_templ
    done
}

# returns true iff the given template has been loaded
loaded () {
    (( $+TEMPLATES[(r)$1] ))
}

# run the chain of functions of the templates for the phase
# it gets called from
# Param: -r -> remove the phase of this template
super () {
    zparseopts -D r+:=removes
    local caller=$funcstack[2] funs=

    if (( $+PHASES[(r)$caller] )); then
        removes=(${removes/-r/})
        if [[ -n $removes ]]; then
            removes=(${removes/%/_$caller})
            funs=(${(s.:.)PHASE_FUNS[$caller]})
            funs=(${funs:|removes})
            eval ${(F)funs}
        else
            _$caller
        fi
    else
        log "'super' called from non-phase '$caller'"
    fi
}

# function, that is used to _export_ the default phase functions
# i.e. 'EXPORT prepare' in template 'bla' will add bla_prepare to the functions being called
# on prepare()
# NB: this relies on CUR_TEMPLATE being correct -- DO NOT set CUR_TEMPLATE in a game file!
EXPORT () {
    local override
    local fun

    for phase; do
        if [[ $phase == *_override ]]; then
            override=1
            phase=${phase%_override}
        else
            override=0
        fi

        fun=${CUR_TEMPLATE}_${phase}

        if (( $+PHASES[(r)$phase] )); then
            if (( override )); then
                PHASE_FUNS[$phase]=$fun
            else
                PHASE_FUNS[$phase]+=:$fun
            fi
        else
            log "Invalid phase function '$phase' exported in $CUR_TEMPLATE"
        fi
    done
}

OVERRIDE () {
    EXPORT ${argv/%/_override}
}

# default enviroment {{{1

ENV[DISPLAY]=":1"

(( PLAY_DEBUG <= 1 )) && ENV[WINEDEBUG]="-all"

# phase functions {{{1

# starts a new X
# if overridden, this MUST call `$BIN --in-X`
play_startX () {
    exc startx $BIN --in-X $GAME -- $DISPLAY -ac -br -quiet ${=X_ARGS}
}

# populate the environment
play_setenv () {
    # set environment
    # ENV is set directly -- EENV is evaluated
    # it is possible to override ENV[p] by PLAY_ENV_p
    # (and similar for EENV)
    for e in ${(k)ENV}; do
        set_env $e
    done
    
    for e in ${(k)EENV}; do
        set_eenv $e
    done
}

# run game
play_run () {
    exc $EXE "$ARGS"
}

# manipulate the newly created X instance
play_setupX () {
    # set display size
    [[ -n $SIZE ]] && exc xrandr -s $SIZE
}

# prepare things for the game, e.g. mount ISOs
play_prepare () {
}

# cleanup after yourself
play_cleanup () {
}

OVERRIDE $PHASES[@]

for phase in $PHASES; do
    functions[$phase]="_$phase \$@"
done

# internal functions {{{1

_load () { # {{{2
    inherit -e default
    source $GAME_PATH

    local funs

    for phase in $PHASES; do
        funs=(${(s.:.)PHASE_FUNS[$phase]})
        funs=(${funs/%/ \$@})
        functions[_$phase]=${(F)funs}
    done
}

_list () { # {{{2
    out "The installed games are:"
    # on -> sort alphabetically
    # N -> NULL_GLOB -> no error message if no match
    # .,@ -> regular files or symbolic links (, == or)
    # :t -> modifier: only basename
    for k in $PLAY_GAMES/*(onN.,@:t); do
        echo "\t> $k"
    done
}

_new () { # {{{2
    local GAME=$1
    local DGAME="$PLAY_GAMES/$GAME"
    local EXE=$2
    local PREFIX=${${3}:-$GAME}
    local convpath

    [[ -e $DGAME ]] && die "Game file already existing -- aborting!"

    inherit -e default wine
    setenv
    
    [[ ! -e $WINEPREFIX ]] && die "Specified prefix '$PREFIX' does not exist"

    convpath="$(exc winepath -u $EXE)"

    [[ ! -e $convpath ]] && die "Specified executable does not exist"

    [[ -n $3 ]] && GPREFIX="PREFIX=\"$3\""

    # everything is fine -- write file
    cat > $DGAME << EOF
inherit wine

$GPREFIX
EXE="$EXE"

# vim:ft=sh
EOF

    out "New game successfully created"
    out "You can play it by '$PLAY_BIN $GAME'"
    out -n "Play it now? [y/n] "
    if read -q; then
        echo
        exc -e $BIN $GAME
    else
        echo
    fi
}

_continue_in_X () { # {{{2
    _load
    setupX
    run
}

_run () { #{{{2
    declare -xg GAME=$1
    declare -xg GAME_PATH="$PLAY_GAMES/$GAME"

    if [[ $GAME == new ]]; then
        shift
        _new "$@"
    elif [[ -z $GAME || ! -e $GAME_PATH ]]; then
        [[ ! -e $GAME_PATH ]] && out "Game '$GAME' not found"
        _list
        exit 1
    else
        if [[ $2 == "cleanup" ]]; then
            out "Cleaning up after '$GAME'"
            _load
            setenv
            cleanup force
        else
            out "Launching '$GAME'"
            _load
            setenv
            prepare
            startX
            cleanup
        fi
    fi
}

# main {{{1
if [[ $1 == "--in-X" ]]; then
    _continue_in_X
else
    _run "$@"
fi
# }}}1

# vim: foldmethod=marker