diff --git a/Makefile b/Makefile
index bb55b0c..0122087 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,2 @@
 example-start:
-	go run example/tracker.go
\ No newline at end of file
+	go run example/example.go
\ No newline at end of file
diff --git a/example/tracker.go b/example/example.go
similarity index 54%
rename from example/tracker.go
rename to example/example.go
index 014ad5d..9a0d000 100644
--- a/example/tracker.go
+++ b/example/example.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/prashantgupta24/activity-tracker/src/activity"
+	"github.com/prashantgupta24/activity-tracker/pkg/tracker"
 )
 
 func main() {
@@ -12,7 +12,7 @@ func main() {
 
 	timeToCheck := 5
 
-	activityTracker := &activity.ActivityTracker{
+	activityTracker := &tracker.Instance{
 		TimeToCheck: time.Duration(timeToCheck),
 	}
 	heartbeatCh, quitActivityTracker := activityTracker.Start()
@@ -23,9 +23,14 @@ func main() {
 		select {
 		case heartbeat := <-heartbeatCh:
 			if !heartbeat.IsActivity {
-				fmt.Printf("no activity detected in the last %v seconds\n", int(timeToCheck))
+				fmt.Printf("no activity detected in the last %v seconds\n\n", int(timeToCheck))
 			} else {
-				fmt.Printf("activity detected in the last %v seconds\n", int(timeToCheck))
+				fmt.Printf("activity detected in the last %v seconds. ", int(timeToCheck))
+				fmt.Printf("Activity type:\n")
+				for activity, time := range heartbeat.Activity {
+					fmt.Printf("%v at %v\n", activity.ActivityType, time)
+				}
+				fmt.Println()
 			}
 		case <-timeToKill.C:
 			fmt.Println("time to kill app")
diff --git a/src/mouse/mouse.go b/internal/pkg/mouse/mouse.go
similarity index 81%
rename from src/mouse/mouse.go
rename to internal/pkg/mouse/mouse.go
index dae91b6..b1cb24c 100644
--- a/src/mouse/mouse.go
+++ b/internal/pkg/mouse/mouse.go
@@ -1,6 +1,8 @@
 package mouse
 
-import "github.com/go-vgo/robotgo"
+import (
+	"github.com/go-vgo/robotgo"
+)
 
 type Position struct {
 	MouseX int
diff --git a/internal/pkg/service/mouseClickHandler.go b/internal/pkg/service/mouseClickHandler.go
new file mode 100644
index 0000000..1994828
--- /dev/null
+++ b/internal/pkg/service/mouseClickHandler.go
@@ -0,0 +1,64 @@
+package service
+
+import (
+	"log"
+	"time"
+
+	"github.com/go-vgo/robotgo"
+	"github.com/prashantgupta24/activity-tracker/pkg/activity"
+)
+
+type MouseClickHandler struct {
+	tickerCh chan struct{}
+}
+
+func (m *MouseClickHandler) Start(activityCh chan *activity.Type) {
+	m.tickerCh = make(chan struct{})
+	registrationFree := make(chan struct{})
+
+	go func() {
+		go addMouseClickRegistration(activityCh, registrationFree) //run once before first check
+		for range m.tickerCh {
+			log.Printf("mouse clicker checked at : %v\n", time.Now())
+			select {
+			case _, ok := <-registrationFree:
+				if ok {
+					//log.Printf("registration free for mouse click \n")
+					go addMouseClickRegistration(activityCh, registrationFree)
+				} else {
+					//log.Printf("error : channel closed \n")
+					return
+				}
+			default:
+				//log.Printf("registration is busy for mouse click handler, do nothing\n")
+			}
+		}
+		log.Printf("stopping click handler")
+		return
+	}()
+}
+
+func (m *MouseClickHandler) Trigger() {
+	//doing it the non-blocking sender way
+	select {
+	case m.tickerCh <- struct{}{}:
+	default:
+		//service is blocked, handle it somehow?
+	}
+}
+func (m *MouseClickHandler) Close() {
+	close(m.tickerCh)
+}
+
+func addMouseClickRegistration(activityCh chan *activity.Type, registrationFree chan struct{}) {
+	log.Printf("adding reg \n")
+	mleft := robotgo.AddEvent("mleft")
+	if mleft {
+		//log.Printf("mleft clicked \n")
+		activityCh <- &activity.Type{
+			ActivityType: activity.MOUSE_LEFT_CLICK,
+		}
+		registrationFree <- struct{}{}
+		return
+	}
+}
diff --git a/internal/pkg/service/mouseCursorHandler.go b/internal/pkg/service/mouseCursorHandler.go
new file mode 100644
index 0000000..abba577
--- /dev/null
+++ b/internal/pkg/service/mouseCursorHandler.go
@@ -0,0 +1,76 @@
+package service
+
+import (
+	"log"
+	"time"
+
+	"github.com/prashantgupta24/activity-tracker/internal/pkg/mouse"
+	"github.com/prashantgupta24/activity-tracker/pkg/activity"
+)
+
+type MouseCursorHandler struct {
+	tickerCh chan struct{}
+}
+
+type cursorInfo struct {
+	didCursorMove   bool
+	currentMousePos *mouse.Position
+}
+
+func (m *MouseCursorHandler) Start(activityCh chan *activity.Type) {
+
+	m.tickerCh = make(chan struct{})
+
+	go func() {
+		lastMousePos := mouse.GetPosition()
+		for range m.tickerCh {
+			log.Printf("mouse cursor checked at : %v\n", time.Now())
+			commCh := make(chan *cursorInfo)
+			go checkCursorChange(commCh, lastMousePos)
+			select {
+			case cursorInfo := <-commCh:
+				if cursorInfo.didCursorMove {
+					activityCh <- &activity.Type{
+						ActivityType: activity.MOUSE_CURSOR_MOVEMENT,
+					}
+					lastMousePos = cursorInfo.currentMousePos
+				}
+			case <-time.After(timeout * time.Millisecond):
+				//timeout, do nothing
+				log.Printf("timeout happened after %vms while checking mouse cursor handler", timeout)
+			}
+		}
+		log.Printf("stopping cursor handler")
+		return
+	}()
+}
+
+func (m *MouseCursorHandler) Trigger() {
+	//doing it the non-blocking sender way
+	select {
+	case m.tickerCh <- struct{}{}:
+	default:
+		//service is blocked, handle it somehow?
+	}
+}
+func (m *MouseCursorHandler) Close() {
+	close(m.tickerCh)
+}
+
+func checkCursorChange(commCh chan *cursorInfo, lastMousePos *mouse.Position) {
+	currentMousePos := mouse.GetPosition()
+	//log.Printf("current mouse position: %v\n", currentMousePos)
+	//log.Printf("last mouse position: %v\n", lastMousePos)
+	if currentMousePos.MouseX == lastMousePos.MouseX &&
+		currentMousePos.MouseY == lastMousePos.MouseY {
+		commCh <- &cursorInfo{
+			didCursorMove:   false,
+			currentMousePos: nil,
+		}
+	} else {
+		commCh <- &cursorInfo{
+			didCursorMove:   true,
+			currentMousePos: currentMousePos,
+		}
+	}
+}
diff --git a/internal/pkg/service/screenChangeHandler.go b/internal/pkg/service/screenChangeHandler.go
new file mode 100644
index 0000000..788e735
--- /dev/null
+++ b/internal/pkg/service/screenChangeHandler.go
@@ -0,0 +1,79 @@
+package service
+
+import (
+	"log"
+	"time"
+
+	"github.com/go-vgo/robotgo"
+	"github.com/prashantgupta24/activity-tracker/pkg/activity"
+)
+
+type ScreenChangeHandler struct {
+	tickerCh chan struct{}
+}
+
+type screenInfo struct {
+	didScreenChange   bool
+	currentPixelColor string
+}
+
+func (s *ScreenChangeHandler) Start(activityCh chan *activity.Type) {
+
+	s.tickerCh = make(chan struct{})
+
+	go func() {
+		screenSizeX, screenSizeY := robotgo.GetScreenSize()
+		pixelPointX := int(screenSizeX / 2)
+		pixelPointY := int(screenSizeY / 2)
+		lastPixelColor := robotgo.GetPixelColor(pixelPointX, pixelPointY)
+		for range s.tickerCh {
+			log.Printf("screen change checked at : %v\n", time.Now())
+			commCh := make(chan *screenInfo)
+			go checkScreenChange(commCh, lastPixelColor, pixelPointX, pixelPointY)
+			select {
+			case screenInfo := <-commCh:
+				if screenInfo.didScreenChange {
+					activityCh <- &activity.Type{
+						ActivityType: activity.SCREEN_CHANGE,
+					}
+					lastPixelColor = screenInfo.currentPixelColor
+				}
+			case <-time.After(timeout * time.Millisecond):
+				//timeout, do nothing
+				log.Printf("timeout happened after %vms while checking screen change handler", timeout)
+			}
+		}
+		log.Printf("stopping screen change handler")
+		return
+	}()
+}
+
+func (s *ScreenChangeHandler) Trigger() {
+	//doing it the non-blocking sender way
+	select {
+	case s.tickerCh <- struct{}{}:
+	default:
+		//service is blocked, handle it somehow?
+	}
+}
+func (s *ScreenChangeHandler) Close() {
+	close(s.tickerCh)
+}
+
+func checkScreenChange(commCh chan *screenInfo, lastPixelColor string, pixelPointX, pixelPointY int) {
+	currentPixelColor := robotgo.GetPixelColor(pixelPointX, pixelPointY)
+	// log.Printf("current pixel color: %v\n", currentPixelColor)
+	// log.Printf("last pixel color: %v\n", lastPixelColor)
+	//robotgo.MoveMouse(pixelPointX, pixelPointY)
+	if lastPixelColor != currentPixelColor {
+		commCh <- &screenInfo{
+			didScreenChange:   true,
+			currentPixelColor: currentPixelColor,
+		}
+	} else { //comment this section out to test timeout logic
+		commCh <- &screenInfo{
+			didScreenChange:   false,
+			currentPixelColor: "",
+		}
+	}
+}
diff --git a/internal/pkg/service/service.go b/internal/pkg/service/service.go
new file mode 100644
index 0000000..d8890c5
--- /dev/null
+++ b/internal/pkg/service/service.go
@@ -0,0 +1,13 @@
+package service
+
+import "github.com/prashantgupta24/activity-tracker/pkg/activity"
+
+const (
+	timeout = 100 //ms
+)
+
+type Instance interface {
+	Start(chan *activity.Type)
+	Trigger()
+	Close()
+}
diff --git a/pkg/activity/types.go b/pkg/activity/types.go
new file mode 100644
index 0000000..aeded92
--- /dev/null
+++ b/pkg/activity/types.go
@@ -0,0 +1,13 @@
+package activity
+
+type activityType string
+
+const (
+	MOUSE_CURSOR_MOVEMENT activityType = "cursor-move"
+	MOUSE_LEFT_CLICK      activityType = "left-mouse-click"
+	SCREEN_CHANGE         activityType = "screen-change"
+)
+
+type Type struct {
+	ActivityType activityType
+}
diff --git a/pkg/tracker/tracker.go b/pkg/tracker/tracker.go
new file mode 100644
index 0000000..dc8c8cd
--- /dev/null
+++ b/pkg/tracker/tracker.go
@@ -0,0 +1,96 @@
+package tracker
+
+import (
+	"log"
+	"time"
+
+	"github.com/prashantgupta24/activity-tracker/internal/pkg/service"
+	"github.com/prashantgupta24/activity-tracker/pkg/activity"
+)
+
+const (
+	preHeartbeatTime = time.Millisecond * 100
+)
+
+func (tracker *Instance) Start() (heartbeatCh chan *Heartbeat, quit chan struct{}) {
+
+	//register service handlers
+	tracker.registerHandlers(&service.MouseClickHandler{}, &service.MouseCursorHandler{},
+		&service.ScreenChangeHandler{})
+
+	//returned channels
+	heartbeatCh = make(chan *Heartbeat, 1)
+	quit = make(chan struct{})
+
+	go func(tracker *Instance) {
+		timeToCheck := tracker.TimeToCheck
+		//tickers
+		tickerHeartbeat := time.NewTicker(time.Second * timeToCheck)
+		tickerWorker := time.NewTicker(time.Second*timeToCheck - preHeartbeatTime)
+
+		activities := makeActivityMap()
+
+		for {
+			select {
+			case <-tickerWorker.C:
+				log.Printf("tracker worker working at %v\n", time.Now())
+				//time to trigger all registered services
+				for service := range tracker.services {
+					service.Trigger()
+				}
+			case <-tickerHeartbeat.C:
+				log.Printf("tracker heartbeat checking at %v\n", time.Now())
+				var heartbeat *Heartbeat
+				if len(activities) == 0 {
+					//log.Printf("no activity detected in the last %v seconds ...\n", int(timeToCheck))
+					heartbeat = &Heartbeat{
+						IsActivity: false,
+						Activity:   nil,
+						Time:       time.Now(),
+					}
+				} else {
+					//log.Printf("activity detected in the last %v seconds ...\n", int(timeToCheck))
+					heartbeat = &Heartbeat{
+						IsActivity: true,
+						Activity:   activities,
+						Time:       time.Now(),
+					}
+
+				}
+				heartbeatCh <- heartbeat
+				activities = makeActivityMap() //reset the activities map
+			case activity := <-tracker.activityCh:
+				activities[activity] = time.Now()
+				//log.Printf("activity received: %#v\n", activity)
+			case <-quit:
+				log.Printf("stopping activity tracker\n")
+				//close all services for a clean exit
+				for service := range tracker.services {
+					service.Close()
+				}
+				return
+			}
+		}
+	}(tracker)
+
+	return heartbeatCh, quit
+}
+
+func makeActivityMap() map[*activity.Type]time.Time {
+	activityMap := make(map[*activity.Type]time.Time)
+	return activityMap
+}
+
+func (tracker *Instance) registerHandlers(services ...service.Instance) {
+
+	tracker.services = make(map[service.Instance]bool)
+	tracker.activityCh = make(chan *activity.Type, len(services)) // number based on types of activities being tracked
+
+	for _, service := range services {
+		service.Start(tracker.activityCh)
+		if _, ok := tracker.services[service]; !ok { //duplicate registration prevention
+			tracker.services[service] = true
+		}
+
+	}
+}
diff --git a/pkg/tracker/types.go b/pkg/tracker/types.go
new file mode 100644
index 0000000..dd46178
--- /dev/null
+++ b/pkg/tracker/types.go
@@ -0,0 +1,20 @@
+package tracker
+
+import (
+	"time"
+
+	"github.com/prashantgupta24/activity-tracker/internal/pkg/service"
+	"github.com/prashantgupta24/activity-tracker/pkg/activity"
+)
+
+type Instance struct {
+	TimeToCheck time.Duration
+	activityCh  chan *activity.Type
+	services    map[service.Instance]bool
+}
+
+type Heartbeat struct {
+	IsActivity bool
+	Activity   map[*activity.Type]time.Time
+	Time       time.Time
+}
diff --git a/src/activity/activityTracker.go b/src/activity/activityTracker.go
deleted file mode 100644
index d7bbee5..0000000
--- a/src/activity/activityTracker.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package activity
-
-import (
-	"log"
-	"time"
-
-	"github.com/go-vgo/robotgo"
-	"github.com/prashantgupta24/activity-tracker/src/mouse"
-)
-
-func (tracker *ActivityTracker) Start() (heartbeatCh chan *Heartbeat, quit chan struct{}) {
-
-	comm, quitMouseClickHandler := isMouseClicked(tracker)
-
-	heartbeatCh = make(chan *Heartbeat, 1)
-	quit = make(chan struct{})
-
-	go func(tracker *ActivityTracker, heartbeatCh chan *Heartbeat, quit chan struct{}) {
-		timeToCheck := tracker.TimeToCheck
-		ticker := time.NewTicker(time.Second * timeToCheck)
-		isIdle := true
-		lastMousePos := mouse.GetPosition()
-		for {
-			select {
-			case <-ticker.C:
-				//log.Printf("tracker checking at %v\n", time.Now())
-				currentMousePos := mouse.GetPosition()
-				var heartbeat *Heartbeat
-				if isIdle && isPointerIdle(currentMousePos, lastMousePos) {
-					//log.Printf("no activity detected in the last %v seconds ...\n", int(timeToCheck))
-					heartbeat = &Heartbeat{
-						IsActivity: false,
-						Time:       time.Now(),
-					}
-				} else {
-					//log.Printf("activity detected in the last %v seconds ...\n", int(timeToCheck))
-					heartbeat = &Heartbeat{
-						IsActivity: true,
-						Time:       time.Now(),
-					}
-					lastMousePos = currentMousePos
-				}
-				heartbeatCh <- heartbeat
-				isIdle = true
-			case <-comm:
-				isIdle = false
-				//log.Printf("value received: %v\n", isIdle)
-			case <-quit:
-				log.Printf("stopping activity tracker\n")
-				quitMouseClickHandler <- struct{}{}
-				//robotgo.StopEvent()
-				return
-			}
-		}
-	}(tracker, heartbeatCh, quit)
-
-	return heartbeatCh, quit
-}
-
-func isPointerIdle(currentMousePos, lastMousePos *mouse.Position) bool {
-	//log.Printf("current mouse position: %v\n", currentMousePos)
-	//log.Printf("last mouse position: %v\n", lastMousePos)
-	if currentMousePos.MouseX == lastMousePos.MouseX &&
-		currentMousePos.MouseY == lastMousePos.MouseY {
-		return true
-	}
-	return false
-}
-
-func isMouseClicked(tracker *ActivityTracker) (clickComm, quit chan struct{}) {
-	ticker := time.NewTicker(time.Second * tracker.TimeToCheck)
-	clickComm = make(chan struct{}, 1)
-	quit = make(chan struct{})
-	registrationFree := make(chan struct{})
-	go func() {
-		isRunning := false
-		for {
-			select {
-			case <-ticker.C:
-				//log.Printf("mouse clicker ticked at : %v\n", time.Now())
-				if !isRunning {
-					isRunning = true
-					go func(registrationFree chan struct{}) {
-						//log.Printf("adding reg \n")
-						mleft := robotgo.AddEvent("mleft")
-						if mleft {
-							//log.Printf("mleft clicked \n")
-							clickComm <- struct{}{}
-							registrationFree <- struct{}{}
-							return
-						}
-					}(registrationFree)
-				}
-
-				select {
-				case _, ok := <-registrationFree:
-					if ok {
-						//log.Printf("registration free for mouse click \n")
-						isRunning = false
-					} else {
-						//log.Printf("Channel closed \n")
-					}
-				default:
-					//log.Printf("registration is busy for mouse click handler\n")
-					isRunning = true
-				}
-
-			case <-quit:
-				log.Printf("stopping click handler")
-				close(clickComm)
-				return
-			}
-		}
-	}()
-	return clickComm, quit
-}
diff --git a/src/activity/types.go b/src/activity/types.go
deleted file mode 100644
index daad7d5..0000000
--- a/src/activity/types.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package activity
-
-import "time"
-
-type ActivityTracker struct {
-	TimeToCheck time.Duration
-}
-
-type Heartbeat struct {
-	IsActivity bool
-	Time       time.Time
-}