i3man 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. #!/usr/bin/ruby
  2. require 'optparse'
  3. Workspace = Struct.new( "Workspace", :number, :name, :visible )
  4. #$max_ws = 20
  5. $max_ws = 500
  6. $min_ws = 0
  7. # External i3 calls
  8. def i3_switch_to_workspace( workspace_number )
  9. command = 'i3-msg \'workspace number ' + workspace_number.to_s + '\''
  10. puts command
  11. %x{ #{command} }
  12. end
  13. def i3_move_window_to_workspace( workspace_number )
  14. command = 'i3-msg \'move to workspace number ' + workspace_number.to_s + '\''
  15. puts command
  16. %x{ #{command} }
  17. end
  18. def get_workspaces( )
  19. workspaces = %x{ i3-msg -t get_workspaces }
  20. m = workspaces.scan( /{"num":\d+,"name":"\w*","visible":\w*,/ )
  21. spaces = [ ]
  22. m.each do | s |
  23. # Clean up the output of i3-msg
  24. # Could totally do this with a JSON parser,
  25. # but I don't want the additional deps
  26. s[ "{\"num\":" ] = ""
  27. s[ "\"name\":" ] = ""
  28. s[ "\"visible\":" ] = ""
  29. s = s.split( "," )
  30. s[ 1 ][ "\"" ] = ""
  31. s[ 1 ][ "\"" ] = ""
  32. # Translate from string to bool
  33. if( s[ 2 ] == "true" )
  34. s[ 2 ] = true
  35. elsif( s[ 2 ] == "false" )
  36. s[ 2 ] = false
  37. end
  38. # Translate from string to int
  39. s[ 0 ] = s[ 0 ].to_i
  40. # Add to the list of available workspaces
  41. spaces.push( Workspace.new( s[ 0 ], s[ 1 ], s[ 2 ] ) )
  42. end
  43. return spaces
  44. end
  45. def get_next_largest_workspace( )
  46. workspaces = get_workspaces( )
  47. current = get_current_workspace_num( )
  48. flag = 0
  49. workspaces.each do | w |
  50. if( flag == 1 )
  51. return w.number
  52. elsif( w.number == current )
  53. flag = 1
  54. end
  55. end
  56. # This is the largest workspace
  57. return -1
  58. end
  59. def get_prev_largest_workspace( )
  60. workspaces = get_workspaces( )
  61. current = get_current_workspace_num( )
  62. flag = 0
  63. workspaces.each do | w |
  64. if( w.number == current )
  65. return flag
  66. end
  67. flag = w.number
  68. end
  69. # This is the smallest workspace
  70. return -1
  71. end
  72. def get_current_workspace_num( )
  73. workspaces = get_workspaces( )
  74. current = -1
  75. workspaces.each do | w |
  76. if( w.visible == true )
  77. current = w.number
  78. break
  79. end
  80. end
  81. return current
  82. end
  83. def workspace_exists( ws )
  84. workspaces = get_workspaces( )
  85. workspaces.each do | w |
  86. if( ws == w.number )
  87. return true
  88. end
  89. end
  90. return false
  91. end
  92. # Generate a new workspace and switch to it
  93. # If that workspace already exists, make a new one right after it
  94. def gen_next_free_workspace( )
  95. workspaces = get_workspaces( )
  96. # If this is the only workspace, we want to maximize the distance
  97. # this one and the next. This workspace now is the root of switching
  98. new_ws = 0
  99. if( workspaces.length( ) == 1 && get_current_workspace_num( ) == $max_ws )
  100. new_ws = 0
  101. else
  102. next_largest = get_next_largest_workspace( )
  103. if( next_largest == -1 )
  104. next_largest = $max_ws
  105. end
  106. if( get_current_workspace_num( ) == ( next_largest - 1 ) )
  107. return
  108. end
  109. dist_to_half = ( next_largest - get_current_workspace_num( ) ) / 2
  110. new_ws = get_current_workspace_num( ) + dist_to_half
  111. # Prevent deadlock
  112. dl_flag = -1
  113. while workspace_exists( new_ws ) or new_ws == get_current_workspace_num( )
  114. if( dl_flag == new_ws )
  115. break
  116. end
  117. dl_flag = new_ws
  118. if( new_ws + 1 < next_largest )
  119. new_ws = new_ws + 1
  120. end
  121. end
  122. end
  123. return new_ws
  124. end
  125. # Generate a new workspace and switch to it
  126. # If that workspace already exists, make a new one right after it
  127. def gen_prev_free_workspace( )
  128. workspaces = get_workspaces( )
  129. # If this is the only workspace, we want to maximize the distance
  130. # this one and the next. This workspace now is the root of switching
  131. new_ws = 0
  132. if( workspaces.length( ) == 1 && get_current_workspace_num( ) == 0 )
  133. new_ws = $max_ws
  134. elsif
  135. prev_largest = get_prev_largest_workspace( )
  136. if( prev_largest == -1 )
  137. prev_largest = 0
  138. end
  139. if( get_current_workspace_num( ) == ( prev_largest + 1 ) )
  140. return
  141. end
  142. dist_to_half = ( get_current_workspace_num( ) - prev_largest ) / 2
  143. new_ws = get_current_workspace_num( ) - dist_to_half
  144. while workspace_exists( new_ws )
  145. if( new_ws - 1 > prev_largest )
  146. new_ws = new_ws - 1
  147. end
  148. end
  149. end
  150. return new_ws
  151. end
  152. options = {}
  153. optparse = OptionParser.new do|opts|
  154. options[ :pull ] = false
  155. opts.on( '-p', '--pull', 'Pull the window with you when you shift workspaces' ) do
  156. options[ :pull ] = true
  157. end
  158. options[ :relative ] = false
  159. opts.on( '-r', '--relative', 'Don\'t create a new workspace, only work with ones that exist' ) do
  160. options[ :relative ] = true
  161. end
  162. options[ :direction ] = nil
  163. opts.on( '-d', '--direction ( next|prev)', 'Move in direction specified' ) do |direction|
  164. options[ :direction ] = direction
  165. end
  166. end
  167. optparse.parse!
  168. if( options[ :direction ] == 'prev' )
  169. if( options[ :relative ] )
  170. if( options[ :pull ] )
  171. %x{ i3-msg 'move to workspace prev' }
  172. end
  173. %x{ i3-msg 'workspace prev' }
  174. else
  175. prev_ws = gen_prev_free_workspace( )
  176. if( options[ :pull ] )
  177. i3_move_window_to_workspace( prev_ws )
  178. end
  179. i3_switch_to_workspace( prev_ws )
  180. end
  181. elsif( options[ :direction ] == 'next' )
  182. if( options[ :relative ] )
  183. if( options[ :pull ] )
  184. %x{ i3-msg 'move to workspace next' }
  185. end
  186. %x{ i3-msg 'workspace next' }
  187. else
  188. next_ws = gen_next_free_workspace
  189. if( options[ :pull ] )
  190. i3_move_window_to_workspace( next_ws )
  191. end
  192. i3_switch_to_workspace( next_ws )
  193. end
  194. else
  195. puts "Invalid direction"
  196. end