Skip to content

Commit 661c1c9

Browse files
committed
Replace the 5 app icons by only one and a subsequent startmenu
One icon only is a requirement of Microsoft to get an app into the Microsoft Store.
1 parent 9621d3f commit 661c1c9

File tree

5 files changed

+425
-11
lines changed

5 files changed

+425
-11
lines changed

lib/ruby_installer/runtime.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module RubyInstaller
22
module Runtime
33
autoload :Colors, 'ruby_installer/runtime/colors'
4+
autoload :ConsoleUi, 'ruby_installer/runtime/console_ui'
45
autoload :ComponentsInstaller, 'ruby_installer/runtime/components_installer'
56
autoload :DllDirectory, 'ruby_installer/runtime/dll_directory'
67
autoload :Msys2Installation, 'ruby_installer/runtime/msys2_installation'
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
require "io/console"
2+
require "fiddle"
3+
require "fiddle/import"
4+
5+
module RubyInstaller
6+
module Runtime
7+
class ConsoleUi
8+
# This class is borrowed from reline
9+
class Win32API
10+
DLL = {}
11+
TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG}
12+
POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*'
13+
14+
WIN32_TYPES = "VPpNnLlIi"
15+
DL_TYPES = "0SSI"
16+
17+
def initialize(dllname, func, import, export = "0", calltype = :stdcall)
18+
@proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1')
19+
import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]}
20+
export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)]
21+
calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype]
22+
23+
handle = DLL[dllname] ||= begin
24+
Fiddle.dlopen(dllname)
25+
rescue Fiddle::DLError
26+
raise unless File.extname(dllname).empty?
27+
Fiddle.dlopen(dllname + ".dll")
28+
end
29+
30+
@func = Fiddle::Function.new(handle[func], import, export, calltype)
31+
rescue Fiddle::DLError => e
32+
raise LoadError, e.message, e.backtrace
33+
end
34+
35+
def call(*args)
36+
import = @proto.split("")
37+
args.each_with_index do |x, i|
38+
args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
39+
args[i], = [x].pack("I").unpack("i") if import[i] == "I"
40+
end
41+
ret, = @func.call(*args)
42+
return ret || 0
43+
end
44+
end
45+
46+
class ButtonMatrix
47+
include Colors
48+
49+
attr_accessor :selected
50+
attr_accessor :headline
51+
52+
def initialize(ncols: 3)
53+
@ncols = ncols
54+
@boxes = []
55+
@con = IO.console
56+
@selected = 0
57+
@headline = ""
58+
enable_colors
59+
end
60+
61+
Box = Data.define :text, :action
62+
def add_button(text, &action)
63+
@boxes << Box.new(text, action)
64+
end
65+
66+
def cursor(direction)
67+
case direction
68+
when :left
69+
@selected -= 1 if @selected > 0
70+
when :up
71+
@selected -= @ncols if @selected - @ncols >= 0
72+
when :right
73+
@selected += 1 if @selected + 1 < @boxes.size
74+
when :down
75+
@selected += @ncols if @selected + @ncols < @boxes.size
76+
end
77+
end
78+
79+
def select
80+
@boxes[@selected].action.call
81+
end
82+
83+
def click(x, y)
84+
s = @slines&.[](y)&.[](x)
85+
if s
86+
self.selected = s
87+
true
88+
end
89+
end
90+
91+
BUTTON_BORDERS = {
92+
thin: "┌─┐" +
93+
"│ │" +
94+
"└─┘" ,
95+
fat: "┏━┓" +
96+
"┃ ┃" +
97+
"┗━┛" ,
98+
double: "╔═╗" +
99+
"║ ║" +
100+
"╚═╝" ,
101+
}
102+
103+
private def box_border(ncol, nrow)
104+
box_idx = ncol + nrow * @ncols
105+
selected = box_idx == @selected
106+
border = BUTTON_BORDERS[selected ? :double : :thin]
107+
border = border.each_char.map do |ch|
108+
selected ? green(ch) : blue(ch)
109+
end
110+
box = @boxes[box_idx]&.text
111+
return box, border, box_idx
112+
end
113+
114+
# Paint the boxes
115+
def repaint(width: @con.winsize[1], height: @con.winsize[0])
116+
obw = (width.to_f / @ncols).floor
117+
spw = obw - 4
118+
nrows = (@boxes.size.to_f / @ncols).ceil
119+
obh = (height.to_f / nrows).floor
120+
sph = obh - 2
121+
line = +""
122+
slines = [[]]
123+
nrows.times do |nrow|
124+
@ncols.times do |ncol|
125+
box, border, box_idx = box_border(ncol, nrow)
126+
if box
127+
line += " #{border[0]}" + "#{border[1]}" * spw + "#{border[2]} "
128+
slines.last.append(*([box_idx] * obw))
129+
end
130+
end
131+
line += "\n"
132+
slines << []
133+
sph.times do |spy|
134+
@ncols.times do |ncol|
135+
box, border, box_idx = box_border(ncol, nrow)
136+
if box
137+
text_lines = box.lines.map(&:chomp)
138+
text_pos = spy - (sph - text_lines.size) / 2
139+
text_line = text_lines[text_pos] if text_pos >= 0
140+
text_line ||= ""
141+
spl = (spw - text_line.size) / 2
142+
spr = spw - spl - text_line.size
143+
line += " #{border[3]}" + " " * spl + text_line + " " * spr + "#{border[5]} "
144+
slines.last.append(*([box_idx] * obw))
145+
end
146+
end
147+
line += "\n"
148+
slines << []
149+
end
150+
@ncols.times do |ncol|
151+
box, border, box_idx = box_border(ncol, nrow)
152+
if box
153+
line += " #{border[6]}" + "#{border[7]}" * spw + "#{border[8]} "
154+
slines.last.append(*([box_idx] * obw))
155+
end
156+
end
157+
line += "\n"
158+
slines << []
159+
end
160+
print "\n#{headline}\n"+line.chomp
161+
@slines = slines
162+
end
163+
end
164+
165+
STD_INPUT_HANDLE = -10
166+
STD_OUTPUT_HANDLE = -11
167+
ENABLE_PROCESSED_INPUT = 0x0001
168+
ENABLE_MOUSE_INPUT = 0x0010
169+
ENABLE_QUICK_EDIT_MODE = 0x0040
170+
ENABLE_EXTENDED_FLAGS = 0x0080
171+
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x200
172+
173+
attr_accessor :widget
174+
175+
def initialize
176+
@GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L')
177+
@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
178+
@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
179+
180+
@ev_r, @ev_w = IO.pipe.map(&:binmode)
181+
182+
set_consolemode
183+
184+
register_term_size_change
185+
register_stdin
186+
187+
at_exit do
188+
unset_consolemode
189+
end
190+
end
191+
192+
def set_consolemode
193+
@hConsoleHandle = @GetStdHandle.call(STD_INPUT_HANDLE)
194+
@base_console_input_mode = getconsolemode
195+
setconsolemode(ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS | ENABLE_VIRTUAL_TERMINAL_INPUT)
196+
end
197+
198+
def unset_consolemode
199+
if @base_console_input_mode
200+
setconsolemode(@base_console_input_mode | ENABLE_EXTENDED_FLAGS)
201+
@base_console_input_mode = nil
202+
if block_given?
203+
begin
204+
yield
205+
ensure
206+
set_consolemode
207+
end
208+
end
209+
end
210+
end
211+
212+
# Calling Win32API with console handle is reported to fail after executing some external command.
213+
# We need to refresh console handle and retry the call again.
214+
private def call_with_console_handle(win32func, *args)
215+
val = win32func.call(@hConsoleHandle, *args)
216+
return val if val != 0
217+
218+
@hConsoleHandle = @GetStdHandle.call(STD_INPUT_HANDLE)
219+
win32func.call(@hConsoleHandle, *args)
220+
end
221+
222+
private def getconsolemode
223+
mode = +"\0\0\0\0"
224+
call_with_console_handle(@GetConsoleMode, mode)
225+
mode.unpack1('L')
226+
end
227+
228+
private def setconsolemode(mode)
229+
call_with_console_handle(@SetConsoleMode, mode)
230+
end
231+
232+
private def register_term_size_change
233+
if RUBY_PLATFORM =~ /mingw|mswin/
234+
con = IO.console
235+
old_size = con.winsize
236+
Thread.new do
237+
loop do
238+
new_size = con.winsize
239+
if old_size != new_size
240+
old_size = new_size
241+
@ev_w.write "\x01"
242+
end
243+
sleep 1
244+
end
245+
end
246+
else
247+
Signal.trap('SIGWINCH') do
248+
@ev_w.write "\x01"
249+
end
250+
end
251+
end
252+
253+
private def register_stdin
254+
Thread.new do
255+
str = +""
256+
c = IO.console
257+
while char=c.read(1)
258+
str << char
259+
next if !str.valid_encoding? ||
260+
str == "\e" ||
261+
str == "\e[" ||
262+
str == "\xE0" ||
263+
str.match(/\A\e\x5b<[^Mm]*\z/)
264+
265+
@ev_w.write [2, str.size, str].pack("CCa*")
266+
str = +""
267+
end
268+
end
269+
end
270+
271+
private def handle_key_input(str)
272+
case str
273+
when "\e[D", "\xE0K".b # cursor left
274+
widget.cursor(:left)
275+
when "\e[A", "\xE0H".b # cursor up
276+
widget.cursor(:up)
277+
when "\e[C", "\xE0M".b # cursor right
278+
widget.cursor(:right)
279+
when "\e[B", "\xE0P".b # cursor down
280+
widget.cursor(:down)
281+
when "\r" # enter
282+
unset_consolemode do
283+
widget.select
284+
end
285+
when /\e\x5b<0;(\d+);(\d+)m/ # Mouse left button up
286+
if widget.click($1.to_i - 1, $2.to_i - 2)
287+
widget.repaint
288+
unset_consolemode do
289+
widget.select
290+
end
291+
end
292+
end
293+
widget.repaint
294+
end
295+
296+
private def main_loop
297+
str = +""
298+
while char=@ev_r.read(1)
299+
case char
300+
when "\x01"
301+
widget.repaint
302+
when "\x02"
303+
strlen = @ev_r.read(1).unpack1("C")
304+
str = @ev_r.read(strlen)
305+
306+
handle_key_input(str)
307+
widget.repaint
308+
else
309+
raise "unexpected event: #{char.inspect}"
310+
end
311+
end
312+
end
313+
314+
def run!
315+
widget.repaint
316+
main_loop
317+
end
318+
end
319+
end
320+
end
321+
322+
if $0 == __FILE__
323+
app = RubyInstaller::Runtime::ConsoleUi.new
324+
bm = RubyInstaller::Runtime::ConsoleUi::ButtonMatrix.new ncols: 3
325+
bm.add_button "text1\nabc" do
326+
p :button_1
327+
exit
328+
end
329+
bm.add_button "text2" do
330+
p :button_2
331+
end
332+
bm.add_button "text3\nabc\noollla\n text3\nabc\noollla" do
333+
p :button_3
334+
end
335+
bm.add_button "text4\ndef" do
336+
p :button_4
337+
end
338+
bm.add_button "text5\nabc" do
339+
p :button_5
340+
end
341+
app.widget = bm
342+
app.run!
343+
end

recipes/installer-inno/rubyinstaller.iss.erb

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,7 @@ MouseoverHint=TIP: Mouse over the above options for more detailed information.
9999
WebSiteLabel=Web Site:
100100
SupportGroupLabel=Support group:
101101
WikiLabel=Wiki:
102-
InteractiveRubyTitle=Interactive Ruby
103-
RubyGemsDocumentationServerTitle=RubyGems Documentation Server
104-
StartCmdPromptWithRubyTitle=Start Command Prompt with Ruby
105-
DocumentationTitle=Documentation
106-
APIReferenceTitle=Ruby %1 API Reference
107-
TheBookofRubyTitle=The Book of Ruby
102+
RubyTitle=Ruby <%= package.rubyver2 %>
108103

109104
[Files]
110105
#define InstallerFileList "filelist-ruby-" + RubyVersion + "-" + RubyBuildPlatform + ".iss"
@@ -144,11 +139,7 @@ Filename: "icacls.exe"; Parameters: """{app}\<%= package.msysdir %>\tmp"" /inher
144139
Filename: "{cmd}"; Parameters: "/c mklink /j ssl <%= package.rubyver2 =~ /^3\.[23]$/ ? "bin" : "lib\\ruby\\#{package.rubylibver}" %>\etc\ssl"; WorkingDir: "{app}"; StatusMsg: "Add link to SSL CA certs"; Flags: runhidden
145140

146141
[Icons]
147-
Name: {autoprograms}\{#InstallerName}\{cm:InteractiveRubyTitle}; Filename: {app}\bin\irb.<%= package.rubyver2 < '3.1' ? "cmd" : "bat" %>; IconFilename: {app}\bin\ruby.exe
148-
Name: {autoprograms}\{#InstallerName}\{cm:DocumentationTitle}\{cm:APIReferenceTitle,{#RubyVersion}}; Filename: {app}\share\doc\ruby\html\index.html; IconFilename: {app}\share\doc\ruby\html\images\ruby-doc.ico; Components: rdoc
149-
Name: {autoprograms}\{#InstallerName}\{cm:RubyGemsDocumentationServerTitle}; Filename: {app}\bin\gem.cmd; Parameters: install --conservative webrick rubygems-server & {app}\bin\gem.cmd server --launch; IconFilename: {app}\share\doc\ruby\html\images\ruby-doc.ico; Flags: runminimized
150-
Name: {autoprograms}\{#InstallerName}\{cm:StartCmdPromptWithRubyTitle}; Filename: {sys}\cmd.exe; Parameters: /E:ON /K {app}\bin\ridk enable; WorkingDir: {%HOMEDRIVE}{%HOMEPATH}; IconFilename: {sys}\cmd.exe
151-
Name: {autoprograms}\{#InstallerName}\{cm:UninstallProgram,{#InstallerName}}; Filename: {uninstallexe}
142+
Name: {autoprograms}\{cm:RubyTitle}; Filename: {app}\bin\ruby.exe; Parameters: {app}\bin\startmenu.rb; IconFilename: {app}\bin\ruby.exe
152143

153144
[Components]
154145
Name: "ruby"; Description: "Ruby-<%= package.rubyver %> base files"; Types: custom; Flags: fixed

recipes/sandbox/20-define-import-files.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ self.import_files.merge!({
88
"resources/files/ridk_use/ridk.ps1" => "ridk_use/ridk.ps1",
99
"resources/files/ridk_use/ridk_use.rb" => "ridk_use/ridk_use.rb",
1010
"resources/files/setrbvars.cmd" => "bin/setrbvars.cmd",
11+
"resources/files/startmenu.rb" => "bin/startmenu.rb",
1112
"resources/files/operating_system.rb" => "lib/ruby/#{package.rubylibver}/rubygems/defaults/operating_system.rb",
1213
"resources/icons/ruby-doc.ico" => "share/doc/ruby/html/images/ruby-doc.ico",
1314
"resources/ssl/cacert.pem" => "#{ssl_dir}/cert.pem",

0 commit comments

Comments
 (0)