qcmd 0.1.7 → 0.1.8

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.
@@ -7,138 +7,143 @@ module Qcmd
7
7
  # *_RESPONSE lists are commands that expect responses
8
8
  #
9
9
 
10
- NO_MACHINE_RESPONSE = %w(connect)
11
-
12
- MACHINE_RESPONSE = %w(workspaces)
13
-
14
- WORKSPACE_RESPONSE = %w(
15
- cueLists selectedCues runningCues runningOrPausedCues thump
10
+ MACHINE = %w(
11
+ alwaysReply
12
+ connect
13
+ workingDirectory
14
+ workspaces
16
15
  )
17
16
 
18
- WORKSPACE_NO_RESPONSE = %w(
19
- go stop pause resume reset panic
17
+ WORKSPACE = %w(
18
+ cueLists
19
+ go
20
+ hardStop
21
+ new
22
+ panic
23
+ pause
24
+ reset
25
+ resume
26
+ runningCues
27
+ runningOrPausedCues
28
+ select
29
+ selectedCues
30
+ stop
31
+ thump
32
+ toggleFullScreen
33
+ updates
20
34
  )
21
35
 
22
- ALL_WORKSPACE_COMMANDS = [WORKSPACE_RESPONSE + WORKSPACE_NO_RESPONSE]
23
-
24
36
  # commands that take no args and do not respond
25
- CUE_NO_RESPONSE = %w(
26
- start stop pause resume load preview reset panic
27
- )
28
-
29
- # commands that take args but do not respond
30
- CUE_ARG_NO_RESPONSE = %w(
37
+ CUE = %w(
38
+ actionElapsed
39
+ allowsEditingDuration
40
+ armed
41
+ children
42
+ colorName
43
+ continueMode
44
+ cueTargetId
45
+ cueTargetNumber
46
+ defaultName
47
+ displayName
48
+ duration
49
+ fileTarget
50
+ flagged
51
+ hardStop
52
+ hasCueTargets
53
+ hasFileTargets
54
+ isBroken
55
+ isLoaded
56
+ isPaused
57
+ isRunning
58
+ listName
59
+ load
31
60
  loadAt
61
+ name
62
+ notes
63
+ number
64
+ panic
65
+ pause
66
+ percentActionElapsed
67
+ percentPostWaitElapsed
68
+ percentPreWaitElapsed
69
+ postWait
70
+ postWaitElapsed
71
+ preWait
72
+ preWaitElapsed
73
+ preview
74
+ reset
75
+ resume
76
+ sliderLevel
77
+ sliderLevels
78
+ start
79
+ stop
80
+ togglePause
81
+ type
82
+ uniqueID
83
+ valuesForKeys
32
84
  )
33
85
 
34
- # commands that always expect a response
35
- CUE_RESPONSE = %w(
36
- uniqueID hasFileTargets hasCueTargets allowsEditingDuration isLoaded
37
- isRunning isPaused isBroken preWaitElapsed actionElapsed
38
- postWaitElapsed percentPreWaitElapsed percentActionElapsed
39
- percentPostWaitElapsed type sliderLevels
40
- basics children
86
+ GROUP_CUE = %w(
87
+ playbackPositionId
41
88
  )
42
89
 
43
- # commands that take args but expect a response if given without args
44
- NO_ARG_CUE_RESPONSE = %w(
45
- number name notes cueTargetNumber cueTargetId preWait duration
46
- postWait continueMode flagged armed colorName
90
+ AUDIO_CUE = %w(
91
+ doFade
92
+ doPitchShift
93
+ endTime
94
+ infiniteLoop
95
+ level
96
+ patch
97
+ playCount
98
+ rate
47
99
  sliderLevel
100
+ sliderLevels
101
+ startTime
48
102
  )
49
103
 
50
- # all cue commands that take arguments
51
- CUE_ARG = CUE_ARG_NO_RESPONSE + NO_ARG_CUE_RESPONSE
52
-
53
- ALL_CUE_COMMANDS = CUE_NO_RESPONSE +
54
- CUE_ARG_NO_RESPONSE +
55
- CUE_RESPONSE +
56
- NO_ARG_CUE_RESPONSE
57
-
58
- class << self
59
- def no_machine_response_matcher
60
- @no_machine_response_matcher ||= %r[(#{NO_MACHINE_RESPONSE.join('|')})]
61
- end
62
- def no_machine_response_match command
63
- !!(no_machine_response_matcher =~ command)
64
- end
65
-
66
- def machine_response_matcher
67
- @machine_response_matcher ||= %r[(#{MACHINE_RESPONSE.join('|')})]
68
- end
69
- def machine_response_match command
70
- !!(machine_response_matcher =~ command)
71
- end
72
-
73
- def workspace_response_matcher
74
- @workspace_response_matcher ||= %r[(#{WORKSPACE_RESPONSE.join('|')})]
75
- end
76
- def workspace_response_match command
77
- !!(workspace_response_matcher =~ command)
78
- end
79
-
80
- def cue_response_matcher
81
- @cue_response_matcher ||= %r[(#{CUE_RESPONSE.join('|') })]
82
- end
83
- def cue_response_match command
84
- !!(cue_response_matcher =~ command)
85
- end
86
-
87
- def cue_no_arg_response_matcher
88
- @cue_no_arg_response_matcher ||= %r[(#{NO_ARG_CUE_RESPONSE.join('|') })]
89
- end
90
- def cue_no_arg_response_match command
91
- !!(cue_no_arg_response_matcher =~ command)
92
- end
93
-
94
- def expects_reply? osc_message
95
- address = osc_message.address
96
-
97
- Qcmd.debug "(check #{address} for reply expectation in connection state #{Qcmd.context.connection_state})"
98
-
99
- # debugger
100
-
101
- case Qcmd.context.connection_state
102
- when :none
103
- # shouldn't be dealing with OSC messages when unconnected to
104
- # machine or workspace
105
- response = no_machine_response_match(address)
106
- when :machine
107
- # could be workspace or machine command
108
- response = machine_response_match(address) ||
109
- workspace_response_match(address)
110
- when :workspace
111
- if is_cue_command?(address)
112
- Qcmd.debug "- (checking cue command)"
113
- if osc_message.has_arguments?
114
- Qcmd.debug "- (with arguments)"
115
- response = cue_response_match address
116
- else
117
- Qcmd.debug "- (without arguments)"
118
- response = cue_no_arg_response_match(address) ||
119
- cue_response_match(address)
120
- end
121
- else
122
- Qcmd.debug "- (checking workspace command)"
123
- response = workspace_response_match(address) ||
124
- machine_response_match(address)
125
- end
126
- end
104
+ FADE_CUE = %w(
105
+ level
106
+ sliderLevel
107
+ sliderLevels
108
+ )
127
109
 
128
- response.tap {|value|
129
- msg = value ? "EXPECT REPLY" : "do not expect reply"
130
- Qcmd.debug "- (#{ msg })"
131
- }
132
- end
110
+ MIC_CUE = %w(
111
+ level
112
+ sliderLevel
113
+ sliderLevels
114
+ )
133
115
 
134
- def is_cue_command? address
135
- /cue/ =~ address && !(/Lists/ =~ address || /Cues/ =~ address)
136
- end
116
+ VIDEO_CUE = %w(
117
+ cueSize
118
+ doEffect
119
+ doFade
120
+ doPitchShift
121
+ effect
122
+ effectSet
123
+ endTime
124
+ fullScreen
125
+ infiniteLoop
126
+ layer
127
+ level
128
+ opacity
129
+ patch
130
+ playCount
131
+ preserveAspectRatio
132
+ quaternion
133
+ rate
134
+ scaleX
135
+ scaleY
136
+ sliderLevel
137
+ sliderLevels
138
+ startTime
139
+ surfaceID
140
+ surfaceList
141
+ surfaceSize
142
+ translationX
143
+ translationY
144
+ )
137
145
 
138
- def is_workspace_command? address
139
- /workspace/ =~ address && !(%r[cue/] =~ address || %r[cue_id/] =~ address)
140
- end
141
- end
146
+ ALL_CUES = (CUE + GROUP_CUE + AUDIO_CUE + FADE_CUE + MIC_CUE + VIDEO_CUE).uniq.sort
142
147
 
143
148
  module Help
144
149
  class << self
@@ -147,43 +152,49 @@ module Qcmd
147
152
  Qcmd.print %[
148
153
  #{Qcmd.centered_text(' Available Commands ', '-')}
149
154
 
155
+
150
156
  exit
151
157
 
152
- close qcmd
158
+ Close qcmd.
153
159
 
154
160
 
155
- connect MACHINE_NAME
161
+ connect MACHINE_ID
156
162
 
157
- connect to the machine with name MACHINE_NAME
163
+ Connect to the machine with id MACHINE_ID. This can either be the name of the
164
+ machine shown in the listing or its number on the list. Once a machine is
165
+ connected its name will appear above the prompt.
158
166
 
159
167
 
160
168
  disconnect
161
169
 
162
- disconnect from the current machine and workspace
170
+ Disconnect from the current machine and workspace.
171
+
172
+
173
+ ..
174
+
175
+ Disconnect cue if one is connected. If not, disconnect the current workspace.
176
+ If one is not connected, disconnect from the machine.
163
177
 
164
178
 
165
179
  use WORKSPACE_NAME [PASSCODE]
166
180
 
167
- connect to the workspace with name WORKSPACE_NAME using passcode PASSCODE. A
168
- passcode is only required if the workspace has one enabled.
181
+ Connect to the workspace with name WORKSPACE_NAME given as a double quoted
182
+ string using passcode PASSCODE. A passcode is only required if the workspace
183
+ has one enabled. Once a workspace is connected its name will appear above the
184
+ prompt.
169
185
 
170
186
 
171
187
  workspaces
172
188
 
173
- show a list of the available workspaces for the currently connected machine.
189
+ Show a list of the available workspaces for the currently connected machine.
174
190
 
175
191
 
176
192
  workspace COMMAND [VALUE]
177
193
 
178
- pass the given COMMAND to the connected workspace. The following commands will
179
- act on a workspace but will not return a value:
194
+ Pass the given COMMAND to the connected workspace. The following commands will
195
+ act on a workspace:
180
196
 
181
- #{Qcmd.wrapped_text(Qcmd::Commands::WORKSPACE_NO_RESPONSE.sort.join(', '), :indent => ' ').join("\n")}
182
-
183
- And these commands will not act on a workspace, but will return a value
184
- (usually a list of cues):
185
-
186
- #{Qcmd.wrapped_text((Qcmd::Commands::WORKSPACE_RESPONSE - ['connect']).sort.join(', '), :indent => ' ').join("\n")}
197
+ #{Qcmd.wrapped_text(Qcmd::Commands::WORKSPACE.sort.join(', '), :indent => ' ').join("\n")}
187
198
 
188
199
  * Pro Tip: once you are connected to a workspace, you can just use COMMAND
189
200
  and leave off "workspace" to quickly send the given COMMAND to the connected
@@ -192,27 +203,72 @@ workspace COMMAND [VALUE]
192
203
 
193
204
  cue NUMBER COMMAND [VALUE [ANOTHER_VALUE ...]]
194
205
 
195
- send a command to the cue with the given NUMBER.
206
+ or
207
+
208
+ cue_id ID COMMAND [VALUE [ANOTHER_VALUE ...]]
209
+
210
+ Send a command to the cue with the given NUMBER or ID depending on the way
211
+ you are addressing the cue.
196
212
 
197
- NUMBER can be a string or a number, depending on the command.
213
+ NUMBER can be a double quoted string or a number, depending on the command.
198
214
 
199
215
  COMMAND can be one of:
200
216
 
201
- #{Qcmd.wrapped_text(Qcmd::Commands::ALL_CUE_COMMANDS.sort.join(', '), :indent => ' ').join("\n")}
217
+ #{Qcmd.wrapped_text(Qcmd::Commands::CUE.sort.join(', '), :indent => ' ').join("\n")}
218
+
219
+ Specific types of cues may have different cues available. Here's the commands
220
+ available for different types of cues:
221
+
222
+ Group Cue:
223
+
224
+ #{Qcmd.wrapped_text(Qcmd::Commands::GROUP_CUE.sort.join(', '), :indent => ' ').join("\n")}
225
+
226
+ Audio Cue:
227
+
228
+ #{Qcmd.wrapped_text(Qcmd::Commands::AUDIO_CUE.sort.join(', '), :indent => ' ').join("\n")}
229
+
230
+ Fade Cue:
231
+
232
+ #{Qcmd.wrapped_text(Qcmd::Commands::FADE_CUE.sort.join(', '), :indent => ' ').join("\n")}
233
+
234
+ Mic Cue:
235
+
236
+ #{Qcmd.wrapped_text(Qcmd::Commands::MIC_CUE.sort.join(', '), :indent => ' ').join("\n")}
237
+
238
+ Video Cue:
239
+
240
+ #{Qcmd.wrapped_text(Qcmd::Commands::VIDEO_CUE.sort.join(', '), :indent => ' ').join("\n")}
241
+
242
+ Once a command has been sent to an existing cue, subsequent cue commands will
243
+ be sent to the same cue with needing to repeat the leading "cue NUMBER". Once
244
+ a cue is connected its name will appear above the prompt.
245
+
246
+
247
+ alias COMMAND ACTION
248
+
249
+ Create a new command to use in qcmd! COMMAND should be a single word,
250
+ starting with made of one or more letters, numbers, underscores, and/or
251
+ hyphens. ACTION should be a legit qcmd program surrounded by parentheses,
252
+ which is anything you can type into qcmd. If you want your command to accept
253
+ arguments, use $1, $2, ... $n in place of the argument.
254
+
255
+ For example:
256
+
257
+ > alias cue-rename (cue $1 name "Hello $2")
258
+
259
+ Would create a new command, "cue-rename", that you could use to rename a cue:
202
260
 
203
- Of those commands, only some accept a VALUE. The following commands, if given
204
- a value, will update the cue:
261
+ > cue-rename 10 World
205
262
 
206
- #{Qcmd.wrapped_text(Qcmd::Commands::CUE_ARG.sort.join(', '), :indent => ' ').join("\n")}
263
+ to rename cue number 10 to "Hello World".
207
264
 
208
- Some cues are Read-Only and will return information about a cue:
265
+ We've included a few custom commands so you can see how it works. Aliases are
266
+ stored in ~/.qcmd/settings.json and can be edited from there.
209
267
 
210
- #{Qcmd.wrapped_text(Qcmd::Commands::CUE_RESPONSE.sort.join(', '), :indent => ' ').join("\n")}
211
268
 
212
- Finally, some commands act on a cue, but don't take a VALUE and don't
213
- respond:
269
+ aliases
214
270
 
215
- #{Qcmd.wrapped_text(Qcmd::Commands::CUE_NO_RESPONSE.sort.join(', '), :indent => ' ').join("\n")}
271
+ See all the aliases.
216
272
 
217
273
  ]
218
274
  end
@@ -0,0 +1,83 @@
1
+ require 'fileutils'
2
+
3
+ module Qcmd
4
+ class Configuration
5
+ class << self
6
+ def qcmd_directory
7
+ ".qcmd"
8
+ end
9
+
10
+ def home_directory
11
+ @home_directory ||= begin
12
+ full_path = File.join(File.expand_path('~'), qcmd_directory)
13
+ begin
14
+ if !File.exists?(full_path)
15
+ FileUtils.mkdir_p(full_path)
16
+ end
17
+ full_path
18
+ rescue => ex
19
+ puts "Failed to create qcmd's home directory at #{ full_path }"
20
+ puts ex.message
21
+
22
+ exit 1
23
+ end
24
+ end
25
+ end
26
+
27
+ def open_file_for_appending(fname)
28
+ f = File.new(fname, 'a')
29
+ f.sync = true
30
+ f
31
+ end
32
+
33
+ def config
34
+ @config ||= begin
35
+ if !File.exists?(config_file)
36
+ File.open(config_file, 'w') {|f|
37
+ default = JSON.pretty_generate({'aliases' => Qcmd::Aliases.defaults})
38
+ Qcmd.debug "[Configuration config] writing defaults: #{ default }"
39
+ f.write default
40
+ }
41
+ end
42
+
43
+ JSON.load(File.open(config_file))
44
+ end
45
+ end
46
+
47
+ def update key, value
48
+ config[key] = value
49
+ save
50
+ end
51
+
52
+ def save
53
+ File.open(config_file, 'w') {|conf_file|
54
+ conf_file.write(JSON.pretty_generate(config))
55
+ }
56
+ end
57
+
58
+ # not really config file things, but related to config & settings storage
59
+
60
+ def history
61
+ @history ||= open_file_for_appending(history_file)
62
+ end
63
+
64
+ def log
65
+ @log ||= open_file_for_appending(log_file)
66
+ end
67
+
68
+ # and the actual files
69
+
70
+ def config_file
71
+ File.join(home_directory, "settings.json")
72
+ end
73
+
74
+ def history_file
75
+ File.join(home_directory, "history.log")
76
+ end
77
+
78
+ def log_file
79
+ File.join(home_directory, "debug.log")
80
+ end
81
+ end
82
+ end
83
+ end