glenreesor / HammerBar

A Windows-Like Taskbar for MacOS
14 stars 2 forks source link

HammerBar - A Windows-Like Taskbar for MacOS

HammerBar is a "Spoon" for HammerSpoon that displays a clickable taskbar along the bottom of your screen with support for app launchers and "widgets" that can show the output of arbitrary lua functions.

HammerBar example

Quick Start

You should now have a basic taskbar. Yay!

But of course there's more. Read on....

Widgets, Screens and BundleIds

Other than the actual taskbar, all other functionality is provided by Widgets. Widgets are explicitly added to the Primary screen and Secondary screens using the HammerBar API.

For example, to add a Safari launcher to the left side of the primary screen and a clock to the right side, change your ~/.hammerspoon/init.lua to:

hs.loadSpoon("HammerBar")

spoon.HammerBar:addWidgetsPrimaryScreenLeft({
  spoon.HammerBar.widgets:appLauncher('com.apple.Safari'),
});

spoon.HammerBar:addWidgetsPrimaryScreenRight({
  spoon.HammerBar.widgets.clock(),
})

spoon.HammerBar:start()

Or perhaps you want the Safari launcher only on your primary screen but the clock on all your screens:

hs.loadSpoon("HammerBar")

spoon.HammerBar:addWidgetsPrimaryScreenLeft({
  spoon.HammerBar.widgets:appLauncher('com.apple.Safari'),
});

spoon.HammerBar:addWidgetsPrimaryScreenRight({
  spoon.HammerBar.widgets.clock(),
})

spoon.HammerBar:addWidgetsSecondaryScreenRight({
  spoon.HammerBar.widgets.clock(),
})

spoon.HammerBar:start()

BundleIds

What the heck are BundleIds? Those are strings used by MacOS to identify applications. For example in the examples above we used the BundleId com.apple.Safari.

So you want to add a different application launcher, how do you determine its BundleId? Easy. Make sure the Hammerspoon console is open (click the Hammerspoon icon in the MacOS menu bar and select Console...), then launch your application the normal MacOS way. After it starts, Shift + Click its button in the HammerBar taskbar to get debug information, including the BundleId, printed to the Hammerspoon console.

API

You've already seen most of the HammerBar API, but here it is in full:

addWidgetsPrimaryScreenLeft()

Adds one or more widgets to the left side of the primary screen.

Argument

Examples

addWidgetsPrimaryScreenRight()

Adds one or more widgets to the right side of the primary screen.

Argument

addWidgetsSecondaryScreenLeft()

Adds one or more widgets to the left side of all secondary screens.

Argument

addWidgetsSecondaryScreenRight()

Adds one or more widgets to the right side of all secondary screens.

Argument

setWindowListUpdateInterval()

Sets the polling frequency used to query Hammerspoon for the current list of windows. This defaults to 3s which seems to work well, but setting a smaller interval will result in new windows showing up on the taskbar sooner. There might be a performance hit due to the increased polling (I haven't investigated this potential performance hit).

Argument

setWindowStatusUpdateInterval()

Sets the polling frequency used to query each app present on the taskbar to get it's current state (minimized or not and the window title). This defaults to 1s.

Argument

start()

Starts HammerBar.

Argument

stop()

Cleans up timers and canvases then stops Hammerbar.

Argument

Widgets

As seen above one or more widgets can be added using:

App Launcher

Adds a launcher for the specified app.

Argument

Example

App Menu

Adds a launcher button that shows a menu of the specified applications when clicked.

Argument

Examples

Clock

Adds a simple clock.

Argument

Example

      spoon.HammerBar:addWidgetsPrimaryScreenLeft({
        spoon.HammerBar.widgets:clock(),
      })

Line Graph

Draws a line graph using numbers generated by a lua function you pass in.

Argument

Example

Text

Displays a string generated by a lua function you pass in.

Argument

Example

Xeyes

Adds a pair of eyes that follow your mouse around. I've found this increases my productivity by about 42%. Inspired by the XWindows program of the same name.

The interval for updating the eyes to follow the mouse is dynamic so you can adjust the reactivity and potential performance impact.

Argument

Example

      spoon.HammerBar:addWidgetsPrimaryScreenLeft({
        spoon.HammerBar.widgets:xeyes({
          minInterval = 0.1,
          maxInterval = 3,
        }),
      })

Keybindings

Why does HammerBar sometimes respond slowly?

Good question! I'm pretty sure it has to do with the internal workings of Hammerspoon (the toolkit used by HammerBar).

HammerBar is definitely not an optimal implementation of a MacOS taskbar, however the use of Hammerspoon and Lua provided a pretty low bar for me to create something that does the trick (for me, at least).

Remember that HammerBar is written in an interpreted language (Lua), it has to poll Hammerspoon for a list of windows, and has to poll every application's window to keep the status (minimized or not as well as window title) up to date.

If you have performance suggestions drop me a note!

Sample Complete Configuration

Here's a sample ~/hammerspoon/init.lua that ties it all together with a decent configuration to start from.

hs.loadSpoon("HammerBar")

-- Return the number of seconds past current minute
function getSecondsAfterMinute()
  local handle = io.popen('date "+%S"')
  local result = handle:read('*a')
  handle:close()

  return result
end

-- Return a sum of the current cpu usage of every process
function getCpuUsage()
  local handle = io.popen('ps -e -o %cpu')
  local result = handle:read('*a')
  handle:close()

  local totalCpu = 0
  for cpu in result:gmatch('[^\n]+') do
    if tonumber(cpu) then
      totalCpu = totalCpu + tonumber(cpu)
    end
  end

  return totalCpu
end

-- Add widgets to the left side of the primary screen
spoon.HammerBar:addWidgetsPrimaryScreenLeft({
    spoon.HammerBar.widgets:appMenu({
        appList = {
          { bundleId = 'com.apple.clock', label = 'Clock' },
          { bundleId = 'com.apple.calculator', label = 'Calculator' },
          { bundleId = 'com.apple.iCal', label = 'Calendar' },
        },
    }),
    spoon.HammerBar.widgets:appLauncher('com.apple.Safari'),
    spoon.HammerBar.widgets:appLauncher('com.apple.finder'),
    spoon.HammerBar.widgets:appLauncher('com.apple.launchpad.launcher'),
})

-- Add widgets to the right side of the primary screen
spoon.HammerBar:addWidgetsPrimaryScreenRight({
    spoon.HammerBar.widgets.clock(),
    spoon.HammerBar.widgets:xeyes({
        minInterval = 0.1,
        maxInterval = 3,
    }),
    spoon.HammerBar.widgets:lineGraph({
        title = 'CPU',
        interval = 1,
        maxValues = 100,
        cmd = getCpuUsage,
    }),
    spoon.HammerBar.widgets:text({
        title = 'CPU',
        interval = 1,
        cmd = function() return getCpuUsage() .. '%' end,
    }),
    spoon.HammerBar.widgets:text({
        title = 'Seconds',
        interval = 1,
        cmd = getSecondsAfterMinute,
    }),
})

-- No widgets on secondary screens except a clock on the right side
spoon.HammerBar:addWidgetsSecondaryScreenRight({
    spoon.HammerBar.widgets.clock(),
})

spoon.HammerBar:start()